Oh, April! The first stirrings of Spring and new beginnings. It is appropriate therefore that this month’s column should be on the subject of initialization and declarations.
Unlike C90, C99 allows declarations to be intermixed with executable statements. Since declarations can have initializers that in some cases are run-time expressions, this raises questions about when those expressions are evaluated and when the initialization takes place.
C99 also makes two changes concerning the initialization of structs, unions, and arrays. The first change is that aggregates and unions are no longer limited to being initialized with constant expressions. The second change is a new syntax that allows you to initialize individual members or elements by name, and thus more easily initialize sparse objects and avoid the errors common to the traditional, positional brace-enclosed initializer syntax.
哦,四月!春天第一次的萌动、新的开端。因此本月专栏的主题为声明和初始化是很恰当的。
与C90不同,C99允许声明混合在可执行语句中。由于声明可以有初始器(initializers ),它们有时候是运行时表达式,这导致了这些表达式何时求值、初始化何时发生的问题。
C99 还做了两个改动,涉及结构、联合以及数组的初始化。第一个改动是聚合类型[a]和联合不再局限于使用常量表达式初始化。第二个改动是新的语法允许你按名字来初始化单独的成员或元素,因而能更容易地初始化分散的对象,避免了传统的按位置括号封闭初始器语法中常见的错误。
In this article, the term “constant expression” is going to come up several times. A constant expression is an expression whose value can be calculated before running the program. Typically, the compiler determines the value for constant expressions involving arithmetic types, and the linker or program loader determines the value of constant expressions involving addresses.
Not surprisingly, a constant expression cannot depend upon the value of any variable, since in general you must run the program to determine a variable’s value. The operands of constant expressions are constants, enumeration literals, sizeof where the operand is not a variable length array, or addresses of functions or static variables. (Such addresses are constant by the time the program is loaded into memory.)
A constant expression cannot have side effects, and so cannot use the assignment, increment, decrement, function call, or comma operators, unless the operators are the operand of a sizeof operator, since sizeof does not evaluate the expression that is its operand.
There are places (particularly during initialization) where C requires expressions to be constant expressions. As discussed below, C99 requires that an expression be a constant expression in fewer places than C90.
在本文中,术语“常量表达式”将要出现多次。常量表达式是能够在运行程序之前计算出其值的表达式。通常,编译器决定涉及算术类型的常量表达式的值,链接器或者程序加载器(program loader)决定涉及地址的常量表达式的值。
毫不奇怪,常量表达式不依赖于任何变量的值,因为一般来说,你必须运行程序来确定一个变量的值。常量表达式的运算数是常数、枚举文字、操作数不是变长数组的 sizeof、函数或静态变量的地址(这些地址在程序装载入内存的时候是不变的)。
常量表达式不能有副作用,因此不能使用赋值(assignment)、自增(increment)、自减(decrement)、函数调用或着逗号运算符(comma operators),除非这个运算符是该操作数的sizeof 运算符[b],因为 sizeof 不对操作数就是它本身的表达式求值。
有些地方(特别是初始化),C要求表达式为一个常量表达式。正如下面讨论的,C99要求表达式为常量表达式的地方比C90少。
C99, like C++, does not require that all declarations appear at the start of a block. The items declared by a declaration are not visible until after their declaration, and so cannot be used by any statements or declarations that precede it in the block.
Objects with static storage duration (objects declared with the static or extern keywords or declared at file scope) must be initialized with constant expressions. Such objects are initialized once, which happens before the program starts. (C++ treats these initializations the same way, but also permits run-time initializers with different rules.)
Objects with automatic storage duration (objects declared local to a function or block without the static or extern keywords) are not initialized until their declarations are reached. In fact, the declarations act like assignment statements and evaluate the initializer expression each time the declaration is reached and assign the value to the object declared. If the same declaration declares multiple initialized objects, the “assignments” occur in the order the objects are declared. (C++ also behaves this way.)
Consider Example 1 below. The program prints the values 0, 2, and 4 for j; and 0, 6, and 12 for k.
// Example 1
#include <stdio.h>int main(){int i = 0;
loop:// j and k are initialized each// time their declarations are// reachedint j = 2*i, k = 3*j;printf("%d %d\n", j, k);if (++i < 3) goto loop;
return 0;}
Of course, programs are not only art but also commercial artifacts. If you need your program to compile on platforms whose compilers do not support this C99 feature, you have a good excuse not to use it. However, be aware that this excuse is only a temporary one until the feature is more widespread.
// Example 1
#include <stdio.h>int main(){int i = 0;
loop:// j and k are initialized each// time their declarations are// reachedint j = 2*i, k = 3*j;printf("%d %d\n", j, k);if (++i < 3) goto loop;
return 0;}
void f()
{
// line below is bad C90, but good C99
int array[2] = {g(), h()};
}
C99 does not require that the expression in a brace-enclosed initializer list be evaluated in any particular order. In the last example, function g might be called before h, or h might be called before g. Be careful if your initializer list expressions have side effects, since you will not know the order in which they occur.
void f()
{
// line below is bad C90, but good C99
int array[2] = {g(), h()};
}
struct S1 {
int i;
float f;
int a[2];
};
struct S1 x = {
.f=3.1,
.i=2,
.a[1]=9
};
In C, expressions written using the . and [] operators usually begin with an identifier that is the name of the object against which the . and [] operators are applied. Unlike expressions, designators do not begin with an indication of the object against which the . and [] designators are applied; designators are applied against the current object being initialized.
The current object being initialized changes if you nest brace-enclosed initializers within brace-enclosed initializers. The outermost brace-enclosed initializer initializes the complete object being declared, and any designated initializers that appear at that level of nesting are relative to the complete object. A nested brace-enclosed initializer whose position corresponds to an element or member whose type is array, struct, or union initializes that nested array, struct, or union subobject, and any designated initializer at that level of nesting is relative to that subobject.
Normally, each initializer in an initializer list initializes the next element or member of the object. However, designated initializers permit you to initialize the elements or members in any order. You may mix using and not using designators for the initializers. If an initializer without a designator follows an initializer with a designator, the element or member initialized by the initializer without the designator is the element or member that would normally follow the one specified by the designator.
Consider Example 2 below. All of the arrays in Example 2 are initialized to the same value. The use of designated initializers for a3 permits the initializers to be given in reverse order. The initialization of a4 illustrates how nested brace-enclosed initializers affect the current object to which designators are relative. The initialization for a5 shows the mixing of initializers with and without designators.
//Example 2
struct S2 {
int x, y;
};
// Arrays a1, a2, a3, a4, and a5 are
// initialized to the same values.
struct S2 a1[3] = {1, 2, 3, 4, 5, 6};
struct S2 a2[3] =
{
{1, 2},
{3, 4},
5, 6
};
struct S2 a3[3] =
{
[2].y=6, [2].x=5,
[1].y=4, [1].x=3,
[0].y=2, [0].x=1
};
struct S2 a4[3] =
{
// Current object is all of a4
[0].x=1, [0].y=2,
{
// Current object is a4[1]
x=3, .y=4
}
// Current object is all of a4
// Current position is [2]
5, [2].y=6
};
struct S2 a5[3] =
{
// After [2].x follows [2].y
[2].x=5, 6,
// After [0].x follows [0].y
[0].x=1, 2,
// after [0].y is [1]
3, 4
};
int a6[3] = {[1]=5, [1]=100};
double a7[3][3] =
{[0][0]=1., [1][1]=1., [2][2]=1.};
int a8[] = {[99]=149, [0]=50, 51};
union {int i; float f;} x =
{.f=3.14};
struct S1 {
int i;
float f;
int a[2];
};
struct S1 x = {
.f=3.1,
.i=2,
.a[1]=9
};
//Example 2
struct S2 {
int x, y;
};
// Arrays a1, a2, a3, a4, and a5 are
// initialized to the same values.
struct S2 a1[3] = {1, 2, 3, 4, 5, 6};
struct S2 a2[3] =
{
{1, 2},
{3, 4},
5, 6
};
struct S2 a3[3] =
{
[2].y=6, [2].x=5,
[1].y=4, [1].x=3,
[0].y=2, [0].x=1
};
struct S2 a4[3] =
{
// Current object is all of a4
[0].x=1, [0].y=2,
{
// Current object is a4[1]
x=3, .y=4
}
// Current object is all of a4
// Current position is [2]
5, [2].y=6
};
struct S2 a5[3] =
{
// After [2].x follows [2].y
[2].x=5, 6,
// After [0].x follows [0].y
[0].x=1, 2,
// after [0].y is [1]
3, 4
};
int a6[3] = {[1]=5, [1]=100};
double a7[3][3] =
{[0][0]=1., [1][1]=1., [2][2]=1.};
int a8[] = {[99]=149, [0]=50, 51};
union {int i; float f;} x =
{.f=3.14};
C99 removes some of the restrictions in C90. Declarations need not be grouped at the beginning of a block. Instead they may appear at the point where the item declared is actually needed. Initialization of variables with automatic storage duration acts like assignment, and more such initializations can use run-time expressions rather than constant expressions compared to C90. This promotes program correctness by allowing the elimination of uninitialized variables.
C99 also adds designated initializers, which allow the programmer to express more explicitly which members or elements are initialized, removes the dependency on initializers appearing in a particular order, and allows initializer lists to be more succinct by only containing initializers for non-zero subobjects.
Randy Meyers is consultant providing training and mentoring in C, C++, and Java. He is the current chair of J11, the ANSI C committee, and previously was a member of J16 (ANSI C++) and the ISO Java Study Group. He worked on compilers for Digital Equipment Corporation for 16 years and was Project Architect for DEC C and C++. He can be reached at rmeyers@ix.netcom.com.
Randy Meyers 是为C、C++和JAVA提供培训和指导的顾问。他目前是ANSI C委员会J11的主席,之前是J16(ANSI C++)和ISO JAVA学习小组(ISO Java Study Group)的成员。他曾经在DEC公司(Digital Equipment Corporation)研究编译器长达16年,并且是DEC C和C++的项目架构师。可以通过以下地址与他联系:rmeyers@ix.netcom.com。