1. Trang chủ
  2. » Công Nghệ Thông Tin

Addison Wesley Essential C Sharp_9 ppt

96 173 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 96
Dung lượng 3,23 MB

Nội dung

ptg Pointers and Addresses 835 If the data is an unmanaged variable type but is not fixed, then use the fixed statement to fix a moveable variable. Fixing Data To retrieve the address of a moveable data item, it is necessary to fix, or pin, the data, as demonstrated in Listing 20.14. Listing 20.14: Fixed Statement byte[] bytes = new byte[24]; fixed (byte* pData = &bytes[0]) // pData = bytes also allowed { // } Within the code block of a fixed statement, the assigned data will not move. In this example, bytes will remain at the same address, at least until the end of the fixed statement. The fixed statement requires the declaration of the pointer variable within its scope. This avoids accessing the variable outside the fixed state- ment, when the data is no longer fixed. However, it is the programmer’s responsibility to ensure that he doesn’t assign the pointer to another vari- able that survives beyond the scope of the fixed statement—possibly in an API call, for example. Similarly, using ref or out parameters will be prob- lematic for data that will not survive beyond the method call. Since a string is an invalid referent type, it would appear invalid to define pointers to strings. However, as in C++, internally a string is a pointer to the first character of an array of characters, and it is possible to declare pointers to characters using char*. Therefore, C# allows declar- ing a pointer of type char* and assigning it to a string within a fixed statement. The fixed statement prevents the movement of the string dur- ing the life of the pointer. Similarly, it allows any moveable type that sup- ports an implicit conversion to a pointer of another type, given a fixed statement. You can replace the verbose assignment of &bytes[0] with the abbrevi- ated bytes, as shown in Listing 20.15. From the Library of Wow! eBook ptg Chapter 20: Platform Interoperability and Unsafe Code836 Listing 20.15: Fixed Statement without Address or Array Indexer byte[] bytes = new byte[24]; fixed (byte* pData = bytes) { // } Depending on the frequency and time to execute, fixed statements have the potential to cause fragmentation in the heap because the garbage col- lector cannot compact fixed objects. To reduce this problem, the best practice is to pin blocks early in the execution and to pin fewer large blocks rather than many small blocks. Unfortunately, this has to be tempered with pinning as little as possible for as short a time as possible, to minimize the chance that a collection will happen during the time that the data is pinned. To some extent, .NET 2.0 reduces the problem, due to some addi- tional fragmentation-aware code. Allocating on the Stack You should use the fixed statement on an array to prevent the garbage col- lector from moving the data. However, an alternative is to allocate the array on the call stack. Stack allocated data is not subject to garbage collec- tion or to the finalizer patterns that accompany it. Like referent types, the requirement is that the stackalloc data is an array of unmanaged types. For example, instead of allocating an array of bytes on the heap, you can place it onto the call stack, as shown in Listing 20.16. Listing 20.16: Allocating Data on the Call Stack byte* bytes = stackalloc byte[42];} Because the data type is an array of unmanaged types, it is possible for the runtime to allocate a fixed buffer size for the array and then to restore that buffer once the pointer goes out of scope. Specifically, it allocates sizeof(T) * E, where E is the array size and T is the referent type. Given the requirement of using stackalloc only on an array of unmanaged types, the runtime restores the buffer back to the system simply by unwinding the stack, eliminating the complexities of iterating over the f-reachable queue (see Garbage Collection and Finalization in Chapter 9) and compacting reachable data. Therefore, there is no way to explicitly free stackalloc data. From the Library of Wow! eBook ptg Pointers and Addresses 837 Note that the stack is a precious resource and, although small, running out of stack space will result in a program crashing; every effort should be taken to avoid running out. If a program does run out of stack space, the best thing that can happen is for the program to shut down/crash immediately. Gener- ally, programs have less than 1MB of stack space (possibly a lot less). There- fore, take great care to avoid allocating arbitrarily sized buffers on the stack. Dereferencing a Pointer Accessing the data stored in a variable of a type referred to by a pointer requires that you dereference the pointer, placing the indirection operator prior to the expression. byte data = *pData;, for example, dereferences the location of the byte referred to by pData and returns the single byte at that location. Using this principle in unsafe code allows the unorthodox behavior of modifying the “immutable” string, as shown in Listing 20.17. In no way is this recommended, but it does expose the potential of low-level memory manipulation. Listing 20.17: Modifying an Immutable String string text = "S5280ft"; Console.Write("{0} = ", text); unsafe // Requires /unsafe switch. { fixed (char* pText = text) { char* p = pText; *++p = 'm'; *++p = 'i'; *++p = 'l'; *++p = 'e'; *++p = ' '; *++p = ' '; } } Console.WriteLine(text); The results of Listing 20.17 appear in Output 20.2. OUTPUT 20.2: S5280ft = Smile From the Library of Wow! eBook ptg Chapter 20: Platform Interoperability and Unsafe Code838 In this case, you take the original address and increment it by the size of the referent type (sizeof(char)), using the preincrement operator. Next, you dereference the address using the indirection operator and then assign the location with a different character. Similarly, using the + and – operators on a pointer changes the address by the * sizeof(T) operand, where T is the referent type. Similarly, the comparison operators (==, !=, <, >, <=, and =>) work to compare pointers translating effectively to the comparison of address location values. One restriction on the dereferencing operator is the inability to derefer- ence a void*. The void* data type represents a pointer to an unknown type. Since the data type is unknown, it can’t be dereferenced to another type. Instead, to access the data referenced by a void*, you must convert it to any other pointer type variable and then dereference the later type, for example. You can achieve the same behavior as Listing 20.17 by using the index operator rather than the indirection operator (see Listing 20.18). Listing 20.18: Modifying an Immutable with the Index Operator in Unsafe Code string text; text = "S5280ft"; Console.Write("{0} = ", text); Unsafe // Requires /unsafe switch. { fixed (char* pText = text) { pText[1] = 'm'; pText[2] = 'i'; pText[3] = 'l'; pText[4] = 'e'; pText[5] = ' '; pText[6] = ' '; } } Console.WriteLine(text); The results of Listing 20.18 appear in Output 20.3. OUTPUT 20.3: S5280ft = Smile From the Library of Wow! eBook ptg Summary 839 Modifications such as those in Listing 20.17 and Listing 20.18 lead to unexpected behavior. For example, if you reassigned text to "S5280ft" following the Console.WriteLine() statement and then redisplayed text, the output would still be Smile because the address of two equal string literals is optimized to one string literal referenced by both variables. In spite of the apparent assignment text = "S5280ft"; after the unsafe code in Listing 20.17, the internals of the string assignment are an address assignment of the modified "S5280ft" location, so text is never set to the intended value. Accessing the Member of a Referent Type Dereferencing a pointer makes it possible for code to access the members of the referent type. However, this is possible without the indirection oper- ator (&). As Listing 20.19 shows, it is possible to directly access a referent type’s members using the -> operator (that is, a->b is shorthand for (*a).b). Listing 20.19: Directly Accessing a Referent Type’s Members unsafe { Angle angle = new Angle(30, 18, 0); Angle* pAngle = &angle; System.Console.WriteLine("{0}° {1}' {2}\"", } The results of Listing 20.19 appear in Output 20.4. SUMMARY This chapter’s introduction outlined the low-level access to the underlying operating system that C# exposes. To summarize this, consider the Main() pAngle->Hours, pAngle->Minutes, pAngle->Seconds); OUTPUT 20.4: 30° 18' 0 From the Library of Wow! eBook ptg Chapter 20: Platform Interoperability and Unsafe Code840 function listing for determining whether execution is with a virtual com- puter (see Listing 20.20). Listing 20.20: Designating a Block for Unsafe Code using System.Runtime.InteropServices; class Program { unsafe static int Main(string[] args) { // Assign redpill byte[] redpill = { 0x0f, 0x01, 0x0d, // asm SIDT instruction 0x00, 0x00, 0x00, 0x00, // placeholder for an address 0xc3}; // asm return instruction fixed (byte* matrix = new byte[6], redpillPtr = redpill) { // Move the address of matrix immediately // following the SIDT instruction of memory. *(uint*)&redpillPtr[3] = (uint)&matrix[0]; using (VirtualMemoryPtr codeBytesPtr = new VirtualMemoryPtr(redpill.Length)) { Marshal.Copy( redpill, 0, codeBytesPtr, redpill.Length); MethodInvoker method = (MethodInvoker)Marshal.GetDelegateForFunctionPointer( codeBytesPtr, typeof(MethodInvoker)); method(); } if (matrix[5] > 0xd0) { Console.WriteLine("Inside Matrix!\n"); return 1; } else { Console.WriteLine("Not in Matrix.\n"); return 0; } unsafe { From the Library of Wow! eBook ptg Summary 841 } // fixed } } The results of Listing 20.20 appear in Output 20.5. In this case, you use a delegate to trigger execution of the assembler code. The delegate is declared as follows: delegate void MethodInvoker(); This book has demonstrated the power, flexibility, consistency, and fantastic structure of C#. This chapter demonstrated the ability, in spite of such high-level programming capabilities, to perform very low-level oper- ations as well. Before I end the book, the next chapter briefly describes the underlying execution platform and shifts the focus from the C# language to the broader platform in which C# programs execute. } // unsafe OUTPUT 20.5: Inside Matrix! From the Library of Wow! eBook ptg This page intentionally left blank From the Library of Wow! eBook ptg 843 21 The Common Language Infrastructure NE OF THE FIRST ITEMS that C# programmers encounter beyond the syntax is the context under which a C# program executes. This chap- ter discusses the underpinnings of how C# handles memory allocation and deallocation, type checking, interoperability with other languages, cross- platform execution, and support for programming metadata. In other words, this chapter investigates the Common Language Infrastructure (CLI) on which C# relies both at compile time and during execution. It cov- ers the execution engine that governs a C# program at runtime and how C# fits into a broader set of languages that are governed by the same execution O Common Language Infrastructure 1 What Is the CLI? Base Class Library Common Language Specification Common Type System Common Intermediate Language 2 CLI Implementations 3 C# Compilation Runtime 4 Garbage Collection Type Safety Code Access Security Platform Portability Performance 5 Components Metadata Application Domains Assemblies Manifests Modules From the Library of Wow! eBook ptg Chapter 21: The Common Language Infrastructure844 engine. Because of C#’s close ties with this infrastructure, most of the features that come with the infrastructure are made available to C#. Defining the Common Language Infrastructure (CLI) Instead of generating instructions that a processor can interpret directly, the C# compiler generates instructions in an intermediate language, the Common Intermediate Language (CIL). A second compilation step occurs, generally at execution time, converting the CIL to machine code that the processor can understand. Conversion to machine code is still not sufficient for code execution, however. It is also necessary for a C# pro- gram to execute under the context of an agent. The agent responsible for managing the execution of a C# program is the Virtual Execution System (VES), generally more casually referred to as the runtime. (Note that the runtime in this context does not refer to a time, such as execution time; rather, the runtime—the Virtual Execution System—is an agent responsi- ble for managing the execution of a C# program.) The runtime is responsi- ble for loading and running programs and providing additional services (security, garbage collection, and so on) to the program as it executes. The specification for the CIL and the runtime is contained within an international standard known as the Common Language Infrastructure (CLI). This is a key specification for understanding the context in which a C# program executes and how it can seamlessly interact with other programs and libraries, even when they are written in alternate languages. Note that the CLI does not prescribe the implementation for the standard, but rather identifies the requirements for how a CLI platform should behave once it conforms to the standard. This provides CLI implementers with the flexibility to innovate where necessary, while still providing enough structure that programs created by one platform can execute on a different CLI implementation, and even on a different operating system. NOTE Note the similarity between these two acronyms and the names they stand for. Take care to understand these upfront to avoid confusion later on. From the Library of Wow! eBook [...]... the terms and acronyms that are part of the CLI TABLE 21.2: Common C# -Related Acronyms Acronym Definition Description NET None Microsoft’s implementation of the entire CLI stack Includes the CLR, CIL, and various languages, all of which are CLS-compliant BCL Base Class Library The portion of the CLI specification that defines the collection, threading, console, and other base classes necessary to build... take C+ + programmers a little getting used to is that garbage-collected objects are not necessarily collected deterministically (at well-defined, compile-time-known locations) In fact, objects can be garbage-collected anytime between when they are last accessed and when the program shuts down This includes collection prior to falling out of scope, or waiting until well after an object instance is accessible... execution can focus on adding program features rather than “plumbing” related to memory management Language Contrast: C+ +—Deterministic Destruction The exact mechanics for how the garbage collector works are not part of the CLI specification; therefore, each implementation can take a slightly different approach (In fact, garbage collection is one item not explicitly required by the CLI.) One key concept... compilation from CIL to machine code This component is the just-in-time (JIT) compiler, and jitting can occur when the program is installed or executed Most CLI implementations favor execution-time compilation of the CIL, but the CLI does not specify when the compilation needs to occur In fact, the CLI even allows the CIL to be interpreted rather than compiled, similar to the way many scripting languages...CLI Implementations 845 Contained within the CLI standard are specifications for the following: • The Virtual Execution System (VES, or runtime) • The Common Intermediate Language (CIL) • The Common Type System (CTS) • The Common Language Specification (CLS) • Metadata • The framework This chapter broadens your view of C# to include the CLI, which is critical to how C# programs operate and interact... interpret compiled C# code An additional compilation step is required to convert the result of C# compilation into machine code Furthermore, the execution requires the involvement of an agent that adds additional services to the C# program, services that it was not necessary to code for explicitly All computer languages define syntax and semantics for programming Since languages such as C and C+ + compile... transform the CIL into something that processors can understand Figure 21.1 shows the process In other words, C# compilation requires two steps: 1 Conversion from C# to CIL by the C# compiler 2 Conversion from CIL to instructions that the processor can execute The runtime is able to understand CIL statements and compile them to machine code Generally, a component within the runtime performs this compilation... space between them The use of compression to fill in the space left by de-allocated objects often results in faster instantiation of new objects (than with unmanaged code), because it is not necessary to search through memory to locate space for a new allocation This also decreases the chance of paging because more objects are located in the same page, which improves performance as well The garbage collector... are accessible only by the containing type 3 Assuming you are not the unscrupulous type that is looking for such vulnerabilities From the Library of Wow! eBook 852 Chapter 21: The Common Language Infrastructure ADVANCED TOPIC Circumventing Encapsulation and Access Modifiers Given appropriate permissions, it is possible to circumvent encapsulation and access modifiers via a mechanism known as reflection... object These two categories of types translate directly to C# syntax that provides a means of declaring each type Common Language Specification (CLS) Since the language integration advantages provided by the CTS generally outweigh the costs of implementing it, the majority of source languages support the CTS However, there is also a subset of CTS language conformance called the Common Language Specification . Language (CIL). A second compilation step occurs, generally at execution time, converting the CIL to machine code that the processor can understand. Conversion to machine code is still not sufficient. Primary C# Compilers (Continued) From the Library of Wow! eBook ptg C# Compilation to Machine Code 847 C# Compilation to Machine Code The HelloWorld program listing in Chapter 1 is obviously C# code,. shows the process. In other words, C# compilation requires two steps: 1. Conversion from C# to CIL by the C# compiler 2. Conversion from CIL to instructions that the processor can execute The runtime

Ngày đăng: 19/06/2014, 22:20