Fields are typed memory locations that store the data of a program. The CLI allows the declaration of both instance and static fields. While static fields are associated with a type and shared across all instances of that type, instance fields are associated with a particular instance of that type. When instantiated, the instance has its own copy of that field. The CLI also supports global fields, which are fields declared outside of any type definition. Global fields shall be static. A field is defined by the .field directive (see Partition II, section 21.15).
The <fieldDecl> has the following parts:
Global fields shall have a data label associated with them. This specifies where, in the PE file, the data for that field is located. Static fields of a type may, but do not need to, be assigned a data label. Example (informative): .field private class [.module Counter.dll]Counter counter 15.1 Attributes of FieldsAttributes of a field specify information about accessibility, contract information, [and] interoperation attributes, as well as information on special handling. The following subsections contain additional information on each group of predefined attributes of a field.
15.1.1 Accessibility InformationThe accessibility attributes are assembly, famandassem, family, famorassem, private, compilercontrolled, and public. These attributes are mutually exclusive. Accessibility attributes are described in Partition II, section 8.2. 15.1.2 Field Contract AttributesField contract attributes are initonly, literal, static, and notserialized. These attributes may be combined. Only static fields may be literal. The default is an instance field that may be serialized. static specifies that the field is associated with the type itself rather than with an instance of the type. Static fields can be accessed without having an instance of a type e.g., by static methods. As a consequence, a static field is shared, within an application domain, between all instances of a type, and any modification of this field will affect all instances. If static is not specified, an instance field is created. initonly marks fields which are constant after they are initialized. These fields may only be mutated inside a constructor. If the field is a static field, then it may be mutated only inside the type initializer of the type in which it was declared. If it is an instance field, then it may be mutated only in one of the instance constructors of the type in which it was defined. It may not be mutated in any other method or in any other constructor, including constructors of subclasses. NOTE The VES need not check whether initonly fields are mutated outside the constructors. The VES need not report any errors if a method changes the value of a constant. However, such code is not valid and is not verifiable.
literal specifies that this field represents a constant value; they [literal fields] shall be assigned a value. In contrast to initonly fields, literal fields do not exist at runtime. There is no memory allocated for them. literal fields become part of the metadata but cannot be accessed by the code. literal fields are assigned a value by using the <fieldInit> syntax (see Partition II, section 15.2). NOTE It is the responsibility of tools generating CIL to replace source code references to the literal with its actual value. Hence changing the value of a literal requires recompilation of any code that references the literal. Literal values are, thus, not version-resilient. 15.1.3 Interoperation AttributesThere is one attribute for interoperation with pre-existing native applications; it is platform-specific and shall not be used in code intended to run on multiple implementations of the CLI. The attribute is marshal and specifies that the field's contents should be converted to and from a specified native data type when passed to unmanaged code. Every conforming implementation of the CLI will have default marshalling rules, as well as restrictions on what automatic conversions can be specified using the marshal attribute. See also Partition II, section 14.5.5. NOTE Marshaling of user-defined types is not required of all implementations of the CLI. It is specified in this standard so that implementations which choose to provide it will allow control over its behavior in a consistent manner. While this is not sufficient to guarantee portability of code that uses this feature, it does increase the likelihood that such code will be portable. 15.1.4 Other AttributesThe attribute rtspecialname indicates that the field name shall be treated in a special way by the runtime. RATIONALE There are currently no field names that are required to be marked with rtspecialname. It is provided for extensions, future standardization, and to increase consistency between the declaration of fields and methods (instance and type initializer methods shall be marked with this attribute). The attribute specialname indicates that the field name has special meaning to tools other than the runtime, typically because it marks a name that has meaning for the Common Language Specification (CLS; see Partition I, sections 8.11.3 and 8.11.4). 15.2 Field Init MetadataThe <fieldInit> metadata can be optionally added to a field declaration. The use of this feature may not be combined with a data label. The <fieldInit> information is stored in metadata, and this information can be queried from metadata. But the CLI does not use this information to automatically initialize the corresponding fields. The field initializer is typically used with literal fields (see Partition II, section 15.1.2) or parameters with default values. See Partition II, section 21.9. The following table lists the options for a field initializer. Note that while both the type and the field initializer are stored in metadata, there is no requirement that they match. (Any importing compiler is responsible for coercing the stored value to the target field type). The description column in the table below provides additional information.
Example (informative): The following example shows a typical use of this: .field public static literal valuetype ErrorCodes no_error = int8(0) The field named no_error is a literal of type ErrorCodes (a value type) for which no memory is allocated. Tools and compilers can look up the value and detect that it is intended to be an 8-bit signed integer whose value is 0. 15.3 Embedding Data in a PE FileThere are several ways to declare a data field that is stored in a PE file. In all cases, the .data directive is used. Data can be embedded in a PE file by using the .data directive at the top level.
Data may also be declared as part of a type:
Yet another alternative is to declare data inside a method:
15.3.1 Data DeclarationA .data directive contains an optional data label and the body which defines the actual data. A data label shall be used if the data is to be accessed by the code.
The body consists either of one data item or a list of data items in braces. A list of data items is similar to an array.
A list of items consists of any number of items:
The list may be used to declare multiple data items associated with one label. The items will be laid out in the order declared. The first data item is accessible directly through the label. To access the other items, pointer arithmetic is used, adding the size of each data item to get to the next one in the list. The use of pointer arithmetic will make the application not verifiable. (Each data item shall have a <dataLabel> if it is to be referenced afterward; missing a <dataLabel> is useful in order to insert alignment padding between data items.) A data item declares the type of the data and provides the data in parentheses. If a list of data items contains items of the same type and initial value, the grammar below can be used as a shortcut for some of the types: the number of times the item shall be replicated is put in brackets after the declaration.
Example (informative): The following declares a 32-bit signed integer with value 123: .data theInt = int32(123) The following declares 10 replications of an 8-bit unsigned integer with value 3: .data theBytes = int8 (3) [10] 15.3.2 Accessing Data from the PE FileThe data stored in a PE file using the .data directive can be accessed through a static variable, either global or a member of a type, declared at a particular position of the data:
The data is then accessed by a program as it would access any other static variable, using instructions such as ldsfld, ldsflda, and so on (see Partition III). The ability to access data from within the PE file may be subject to platform-specific rules, typically related to section access permissions within the PE file format itself. Example (informative): The following accesses the data declared in the example of Partition II, section 15.3.1. First a static variable needs to be declared for the data -- e.g., a global static variable: .field public static int32 myInt at theInt Then the static variable can be used to load the data: ldsfld int32 myInt // data on stack 15.3.3 Unmanaged Thread-Local Storage
15.4 Initialization of Non-Literal Static Data
Many languages that support static data (i.e., variables that have a lifetime that is the entire program) provide for a means to initialize that data before the program begins running. There are three common mechanisms for doing this, and each is supported in the CLI. 15.4.1 Data Known at Link TimeWhen the correct value to be stored into the static data is known at the time the program is linked (or compiled, for those languages with no linker step), the actual value can be stored directly into the PE file, typically into the data area (see Partition II, section 15.3). References to the variable are made directly to the location where this data has been placed in memory, using the OS-supplied fixup mechanism to adjust any references to this area if the file loads at an address other than the one assumed by the linker. In the CLI, this technique can be used directly if the static variable has one of the primitive numeric types or is a value type with explicit type layout and no embedded references to managed objects. In this case the data is laid out in the data area as usual, and the static variable is assigned a particular RVA (i.e., offset from the start of the PE file) by using a data label with the field declaration (using the at syntax).
This mechanism, however, does not interact well with the CLI notion of an application domain (see Partition I, section 12.5). An application domain is intended to isolate two applications running in the same OS process from one another by guaranteeing that they have no shared data. Since the PE file is shared across the entire process, any data accessed via this mechanism is visible to all application domains in the process, thus violating the application domain isolation boundary. 15.5 Data Known at Load TimeWhen the correct value is not known until the PE file is loaded (for example, if it contains values computed based on the load addresses of several PE files), it may be possible to supply arbitrary code to run as the PE file is loaded, but this mechanism is platform-specific and may not be available in all conforming implementations of the CLI.
15.5.1 Data Known at RuntimeWhen the correct value cannot be determined until type layout is computed, the user shall supply code as part of a type initializer to initialize the static data. The guarantees about type initialization are covered in Partition II, section 9.5.3.1. As will be explained below, global statics are modelled in the CLI as though they belonged to a type, so the same guarantees apply to both global and type statics. Because the layout of managed types need not occur until a type is first referenced, it is not possible to statically initialize managed types by simply laying the data out in the PE file. Instead, there is a type initialization process that proceeds in the following steps:
Within a type initialization procedure there are several techniques:
|