The New C: Variable Length Arrays, Part 3: Pointers and Parameters

新的C语言:变长数组,第三部分:指针和形参

Randy Meyers


Randy shows that pointers to VLAs are as convenient as pointers to normal arrays. The question is: how many programmers understand pointers to arrays in the first place? Look here to see if you're one of them.

    Randy 展示了指向VLA的指针就跟指向普通数组的指针那样方便。问题是:有多少程序员从一开始就理解了指向数组的指针?在这里看看你是不是其中一个。

Every C programmer knows that pointers and arrays are closely related. In fact, many students learning C wonder how they differ once they are told that you can apply the square bracket indexing operator to both arrays and pointers, and that an array name becomes a pointer to the first element of the array except when the array name is the operand of sizeof or the address of operator (unary &).


    每一个C程序员都知道指针和数组是密切相关的。方括号索引运算符可以作用于指针和数组,并且一个数组的名字变成指向该数组第一个元素的指针除非数组的名字是 sizeof 或是运算符的地址(一元 &)操作数。事实上,许多学习C的学生在被告知以上事实时都会疑惑它们的区别是什么。


Pointer arithmetic is one of the reasons why arrays and pointers are intertwined [1]; another reason is that many operations on arrays cannot be performed on arrays directly. In particular, you cannot pass an entire array as an argument to a function. Instead, a pointer to the array is passed, and the function operates on the array indirectly through the pointer. So close is the relationship between arrays and pointers that C syntax and semantics somewhat obscure the fact that C lacks array parameters.


    指针运算是为何数组与指针相互纠缠[1]的原因之一;另一个原因是许多在数组上的运算不能直接数组上进行。特别是,你不能把整个数组作为实参传递给一个函数。作为替代,传递的是一个指向该数组的指针,并且函数通过这个指针间接地操作该数组。数组和指针之间的关系如此紧密以致于C的语法和语义有点掩盖C缺少数组形参这个事实。


It should not be surprising that the new VLA (variable length array) feature in C99 [2] has the companion feature of pointer to variable array, and that one of the useful places to use a pointer to a VLA is as a parameter to a function.


    不应该感到奇怪,C99中的新的VLA (变长数组)特性[2] 包含相伴的指向可变数组的指针,并且其中一个有用的地方是,使用一个指向VLA的指针作为函数的参数。

Pointer to VLA

指向变长数组的指针

A pointer to VLA can be declared using the syntax similar to pointer to (normal) array:


    一个指向变长数组的指针可以通过跟指向(普通)数组类似的语法来声明:

int (*pa)[10];
int (*pvla)[f()];
pa is a pointer to an array of 10 ints. pvla is a pointer to a VLA of the number of ints given by expression f() when the declaration is reached in the normal flow of control in the program. The difference between a pointer to an array and a pointer to a VLA is that the bounds of the (normal) array is a constant expression [3] while the bounds of the VLA is a run-time expression. Normally such a small difference between a new language feature and an old feature would mean that programmers would have little trouble understanding the new feature. Unfortunately, even though pointers to arrays date back to early C, many programmers are unfamiliar with them. There are two reasons for this unfamiliarity. First, as we will see in the next section, pointers to arrays most naturally occur as function parameters, and C syntax and semantics handle this with so much grace that many programmers fail to notice. Second, while pointers to arrays can be used to process a single dimensional array, it is more natural in C to process such an array using a pointer to the element type. Consider Listing 1, which initializes the elements of an array and a VLA to 1 indirectly through pointers. The pattern in this code should look familiar to even a programmer with little experience with pointers to arrays. A pointer is declared. The pointer is assigned or initialized with a pointer to the object that it is to operate on, usually by applying the & operator to the object to be accessed indirectly. The * operator is applied to the pointer, and the result of the * operator is treated as if it was the original object referenced by the pointer. The only unusual thing about Listing 1 is that applying * to the pointers yields arrays. Listing 2 is the more common way to write the function in Listing 1. In Listing 2, pointers to int are used to process the arrays of ints rather than using pointers to (normal or variable length) arrays of ints. In C, because of pointer arithmetic and the fact that the index operator is defined in terms of pointer arithmetic [1], a pointer of type T can also be used to process an array of type T. As Listing 2 shows, a pointer can process all of the elements of an array merely by indexing the pointer. (Remember, E[i] means *(E + i) regardless of whether E is an array or a pointer expression.) Contrast the initializations of the pointers in Listings 1 and 2. In Listing 1, the initializations of pa and pvla use the & operator on arrays yielding respectively a pointer to an array of three ints and a pointer to a VLA of bounds ints. In Listing 2, the initializations of p1 and p2 just use the array names without the & operator. Whenever an array name appears in an expression except as the operand of unary & or the sizeof operator, the value of the array name becomes a pointer to the first element of the array. More formally, if A is an expression with type array, except when the operand of unary & or sizeof, A has the value and type of &((A)[0]). Thus in Listing 2, p1 and p2 are initialized with pointers to ints. Note that a single dimensional VLA yields a pointer type that carries no hint that it came from a VLA. Given Listings 1 and 2, why are pointers to arrays needed at all? The answer is that pointers to arrays are useful when processing multidimensional arrays. Consider Listing 3. Listing 3 seems to be a cross between Listing 1 and Listing 2 for good reasons. Listing 1 uses pointers to arrays, as does Listing 3. Listing 2 shows how a pointer to type T can be used to process an array with elements of type T, as does Listing 3. The difference between Listing 2 and Listing 3 is that in Listing 3 type T is an array type rather than a basic type like int. In Listing 3, the pointers are pointers to arrays (normal or variable length). When you dereference a pointer to an array, the result is an array (which might then become a pointer to its first element, as described above). When you add one to a pointer to an array, then you move the pointer to the next entire array that follows the one the pointer originally pointed to. When you index a pointer to an array, each index selects an array object. Thus in Listing 3, pa[i] or pvla[i] yields an array object that may be further indexed. As I wrote above, in C, if you have a pointer to type T, you can use it to process an array of type T, even if type T is an array type. Note that when pa and pvla are initialized in Listing 3 that just the array names are used (no & operator). As explained above, the array names become pointers to their first elements. Thus, pa is initialized with &(a[0]), a pointer to an array of three ints. pvla is initialized with &(vla[0]), a pointer to an array of bounds ints. As I discussed in [1], pointer arithmetic in C requires knowing the size of the object that the pointer is pointing to. In Listing 3, the size of the objects pointed to by pa is known at compile time: it is sizeof (int [3]). In contrast, the size of the objects pointed to by pvla is not known at compile time: it is sizeof(int [bounds]). As I discussed in [2], the result of a sizeof operator is computed at run time for a VLA. Not surprisingly, sizeof is also a run-time operation if you ask for the size of a VLA reached indirectly through a pointer. Thus in Listing 3, sizeof (*pvla) is an expression whose value is computed at run time and is equal to sizeof(int [bounds]). If sizeof(int) is 4 and bounds had the value 3, the result of the sizeof expressions would be 12. Note that sizeof (pvla) is not a run-time operation since it is just the size of the pointer pvla itself, which is known at compile time. sizeof(*pvla) is used whenever pointer arithmetic or indexing is done on pvla. This means that the C implementation must record the size of the VLA type that the pointer type points to. Like other VLA types, the size information associated with a pointer to VLA is saved when the declaration is executed and does not change during the declaration’s lifetime. The expression that is the bounds of the VLA is not reevaluated until next time the declaration is executed. Consider Listing 4. (By the way, the “z” in the format is a new C99 modifier that means the argument is size_t or the corresponding signed integer type. Thus, “%zu” prints a size_t number as unsigned.) When run, the program in Listing 4 prints out 10 20 30 even though the value of n changes between when the pointer to VLA is declared and the sizeof expression that yields the size of the array pointed to. However, since each pass through the loop enters and exits the block that is the loop body, each pass of the loop picks up a new value of n for the bounds of the pointer to VLA. Listings 1, 2, and 3 show a useful coding technique. Although from the C implementation’s point of view the bounds of a VLA are fixed from the time its declaration is executed until the lifetime of the declaration ends, that does not mean that the programmer can conveniently compute that bounds later in the program. If the bounds expression of a VLA is complex or might change value, you might want to assign the value of the bounds expression to a local variable and use the local variable as the bounds in the declaration. If you fail to do this, all is not lost: see the discussion of sizeof in [2]. Listing 4 also shows another point about the size information that the C implementation saves for VLAs. That size information is associated with the type and not the value of the pointer to VLA or even the VLA object itself. In Listing 4, pvla is uninitialized stack trash (that is OK since the sizeof expression does not actually evaluate its operand: pvla is never actually dereferenced). Clearly, the size of the array that pvla is suppose to point to is not part of the value of pvla. Likewise, there is no array to which pvla points in Listing 4, so the size is not part of the array object. Instead, every VLA type in a program causes the C implementation to set aside an unnamed variable to hold the size of arrays of that type. (The optimizer might combine several such variables into one if it proves that they hold the same value). Note that this approach uses less memory than making the size information part of the array object itself. Consider the declaration:
    
    pa  是一个指向10个 int 的数组的指针。pvla 是一个指向 int 的VLA指针,其数量在正常控制流到达该声明时由表达式 f() 给出。指向数组的指针跟指向VLA的指针之间的不同点是(正常)数组的边界为一个常量表达式[3]而VLA的边界为一个运行时表达式。通常这些新语言特性和旧特性之间的小差别意味着程序员在理解新特性上会有些小麻烦。不幸的是,即使追溯到早期C中指向数组的指针,许多程序员也不熟悉它们。有两个原因造成这个不熟悉。首先,就如我们将在下一节看到的,指向数组的指针最自然的情况下会作为函数形参而产生,并且C的语法和语义处理得如此优雅以致于程序员没能注意到。其次,当指向数组的指针可以用来处理一维数组时,在C中处理这种数组更自然的方法是使用指向元素类型的指针。考虑 Listing 1,它间接地通过指针把数组和VLA总的元素初始化为1。即使是对指向数组的指针只有一点经验的程序员来说,这样的模式看起来应该很熟悉。声明了一个指针。以一个指向它所操作对象的指针对它赋值或是初始化,通常是对能够间接访问的对象使用 & 运算符。* 运算符作用于该指针,并且该 * 运算符的结果就如同它是指针所引用的原始对象。 Listing 1 中唯一不寻常的事情就是把 * 作用于指针从而得到一个数组。Listing 2 以更常见的方式编写 Listing 1 中的函数。在 Listing 2中,使用指向 int 的指针用来处理 int 的数组,而不是指向(通常的或是变长) int 数组的指针。在C中,由于指针运算以及索引运算符是按照指针运算定义的[1]这个事实,一个类型为T的指针可以用来处理一个类型为T的数组。就如 Listing 2 展示的,一个指针可以简单的通过索引该指针来处理数组中的所有元素(记住,E[i] 表示 *(E + i) 不管E是一个数组还是一个指针表达式)。对比 Listing 12 中的初始化。在 Listing 1 中,pa pvla 的初始化对数组使用一个 & 运算符分别得到一个指向三个 int 的数组的指针以及一个指向 bounds int 的VLA指针。 在 Listing 2 中,p1 p2 的初始化只使用了数组名字而没有用 & 操作符。不论何时表达式中的数组名字,除非作为一元 & 或是 sizeof 运算符的操作数,数组名字的值变成指向该数组第一个元素的指针。更正式地,如果 A 是是一个数组类型的表达式,除非作为一元 & 或是 sizeof 的操作数,A的值和类型为 &((A)[0])。因此在 Listing 2 中,p1 p2 以指向 int 的指针初始化。注意,一维 VLA 得到的指针类型并没有暗示它来自 VLA。考虑到 Listing 12,为什么还需要指向数组的指针?答案是指向数组的指针在处理多维数组时很有用。考虑 Listing 3Listing 3 看上去有很好的理由混合Listing 1Listing 2。Listing 1使用指向数组的指针,Listing 3也是。Listing 2Listing 3 之间的区别是Listing 3 中 类型 T 是一个数组类型,而不是一个如 int 的基本类型。在 Listing 3中,这个指针是一个指向数组的指针(普通的或是变长的)。当你解引用一个指向数组的指针时,结果是一个数组(可能会变成指向其第一个元素的指针,如上面讨论的)。当你对指向指针的数组加一时,你把指针移动跟随在该指针原来指向后面的下一个数组。当你索引一个指向数组的指针,每一个索引选中一个数组对象。因此在 Listing 3 中,pa[i]pval[i] 得到一个将来可能要索引的数组对象。如我前面所写的,在C中,如果你有一个指向类型 T 的指针,你可以使用它来处理类型 T 的数组,即使类型 T 是一个数组类型。注意,在 Listing 3papvla 初始化时只使用了数组名字(没有 & 运算符)。如上面解释的,数组名字变成指向它们第一个元素的指针。因此 pa 是一个指向三个 int 的数组的指针, 以 &(a[0]) 初始化。 pval 是 一个指向 boundsint 的数组,以 &(vla[0]) 初始化。如我在[1]中讨论的,C中的指针运算需要知道指针所指对象的大小。在Listing 3 中,pa 所指对象的大小在编译时已知。相反地 pvla 所指对象的大小在编译时未知:它是  sizeof (int [bounds])。如我在 [2] 中讨论的,对 VLA 的 sizeof 运算符的结果是在运行时计算的。毫不惊讶,sizeof 也是一个运行时操作,如果你询问通过指针间接访问的VAL的大小。因此在 Listing 3 中,sizeof (*pvla) 是一个在运行时计算的表达式,它的值等于 sizoef (int [bounds])。如果 sizeof(int)4 并且 bounds 的值为 3, 那么该 sizeof 的值将是 12。注意,sizeof(pvla) 不是一个运行时操作,因为它只是指针 pvla 本身的大小,在编译时已知。 不论何时在 pvla 上完成指针运算或是索引时使用sizeof (*pvla)。这意味着C实现必须记录该指针类型所指的 VLA 类型的大小。如同其他VLA类型,当一个声明执行时, 保存了与指向VLA的指针有关的大小信息,并且在声明的生存期内不发生改变。VLA的表达式即 bounds 在下一次执行声明以前不再重新计算。考虑Listing 4(顺便说一句,格式中的 "z"是一个C99中新的修饰符,表示参数是 size_t 或是对应的无符号整型。因此,"%zu"以无符号数打印出一个 size_t 的数字)。当运行时,Listing 4 中的程序打印出 10 20 30 即使 n 的值在声明指向VLA的指针以及得到所指数组大小的 sizeof 表达式之间发生了改变。然而,因为每一次通过循环入口和出口也就是循环体,每一次通过循环都为作为指针所指VLA边界的 n 取一个新值。 Listing 123展示了一项有用的编码技术。尽管从C实现的角度来看,VLA的边界从执行声明以后到声明的生存期结束之间都是固定的,但这并不表示程序员可以方便地在以后的程序中计算出这个边界。如果VLA的边界表达式是复合的或是可能该变的值,你可能会想把边界表达式的值赋给一个局部变量,并使用该局部变量作为声明中的边界。如果你没能这么做,一切也没有丢失:参阅[2]中关于 sizeof 的讨论。Listing 4还展示了另一点关于C实现为VLA保存的大小信息。该大小信息跟类型有关,而不是这个指向VLA的指针或者甚至是VLA对象本身。在Listing 4中,pvla 是一个未初始化的堆栈垃圾(这是可以的,因为 sizeof 运算符并不实际对它的操作数求值:实际上决不会解引用 pvla)。很显然,pvla 假定所指的数组的大小不是 pvla 值的一部分。同样地,Listing 4pvla 并不指向一个数组,所以大小并不是不是数组对象的一部分。作为替代,程序中每一个VLA类型都会使C实现留出一个未命名变量来保存该类型数组的大小(优化程序可能会把若干个这样的变量合并成一个,如果它们有相同的大小)。注意,这种方法相对于把大小信息作为数组对象本身一部分来说会使用更少的内存。考虑这个声明:

 int x[m][n];

C needs only storage to hold two sizes: the size of the two-dimensional array x and the size of the elements of x (all of which are the same size). If size was part of the array object (Java does things this way), the x object itself would contain space to store the entire size of the array, and each element of x would contain space to store the size of the subarray that is that element. The Java approach needs to store m+1 lengths while the C approach stores only two. Another advantage of the C approach is since the size information is not part of the object, a VLA has the same size and storage layout as a non-VLA with the same element type and array bounds. Since the size is not part of the array, and VLAs and normal arrays have the same representation, pointers to VLAs are permitted to point at normal arrays and pointers to normal arrays are permitted to point at VLAs. When you assign a pointer to a (normal or variable length) array to a pointer to a (normal or variable length) array, the number of elements and the type of the elements must match. As long as both pointers point to normal arrays, both of these requirements are checked at compile time. If either the source or destination of the assignment is a pointer to a VLA, then only the element types will be checked by the compiler. The number of elements must still match. If they do not, then the program has a run-time error that the C implementation has no obligation to catch: the program might terminate immediately or continue to run with mysterious behavior or even appear to work. Listing 5 shows the four combinations of assignments between pointers to (normal or variable length) arrays. All four pointer assignments are valid, as long as function ex5 is called with the argument 10. Only the first and last assignments are valid if any other argument is passed. Only the first assignment is checked at compile time for the proper number of elements in the array types.

    C只需要存储空间来保存两个大小:二维数组 x 的大小以及 x 元素的大小(所有的大小都相同)。如果大小是数组对象的一部分(Java 的想法与此不同),对象 x 本身将要包含存储整个数组大小的空间,并且 x 的每一个元素都要包含该子数组大小的空间。 Java的方法需要存储 m+1 的长度而C的方法只需要两个。C的方法的另一个好处是,由于大小信息不是对象的一部分,VLA的大小、布局以及数组边界与同样类型元素的非VLA相同。由于大小不是数组的一部分,并且VLA与普通的数组有相同的表示,允许指向VLA的指针指向普通的数组,也允许指向普通数组的指针指向VLA。当你把一个指向(普通或变长)数组的指针赋值为一个指向(普通或变长)数组的指针时,元素的数目以及类型都必须匹配。只要两个指针都指向普通数组,都会在编译时检查这些要求。如果该赋值的源操作数和目的操作数都不是VLA,编译器只会检查元素的类型。元素的数目也必须要匹配。不然的话,该程序存在一个C的实现没有义务去捕捉的运行时错误:程序可能马上终止或者以神秘的行为继续运行或者甚至看上去工作。Listing 5展示了4个指向(普通或变长)数组的指针之间赋值的组合。所有四个指针赋值都是有效的,只要以实参 10 调用函数 ex5。传递任何其他实参时,只有第一个和最后一个赋值是有效的。只有第一个赋值会在编译时就数组类型中合适的元素数目进行检查。

VLA Parameters

变长数组形参

C does not permit function parameters to be arrays; it does not permit entire arrays to be passed as arguments. However, the language finesses this so well that some programmers do not realize this. If a parameter to a function has type array of type T, the C compiler rewrites the declaration of the parameter to be a pointer to type T. The result is exactly as if the programmer wrote the pointer form of the parameter declaration in the first place. This rule is followed whether the array parameter is a normal array or a VLA. Note how the semantics of C dovetail to handle “array” parameters seamlessly. If you write an array parameter, the compiler rewrites the function declaration to have a pointer parameter. This is OK since you can write the body of the function as if the parameter was still an array since the index operator works as well on a pointer as on an array. When you call the function, you can pass an array name as an argument because any expression of type array (except when the operand of unary & or sizeof) becomes a pointer. If the array being passed as an argument was compatible with the original array type of parameter, then the pointer type of the argument will match the pointer rewrite of the parameter. This works the same for VLAs or normal arrays. One somewhat surprising thing is that single-dimensional VLA parameters become plain old pointers after the rewrite. Consider:

    C不允许函数形参为数组;它不允许把整个数组作为实参传递。然而,语言对此处理得非常好以致于一些程序员没有注意到这一点。如果函数的形参的类型是 T 类型的指针,C 编译器把这个形参的声明改写为指向类型 T 的指针。结果就正犹如程序员在一开始就写的就是指针形式的形参声明。不管该形参是普通数组还是VLA都遵循这个规则。注意C的语义如何无缝地衔接处理“数组”形参。如果你写了一个数组形参,编译器改写该函数声明来得到一个指针形参。这是可以的,你可以编写函数的主体,犹如形参还是一个数组,因为索引运算符在指针和数组上都工作。当你调用该函数时,你可以传递一个数组名字作为形参,因为任何该类型数组的表达式(除非作为一元 & 或是 sizeof 的操作数)都变成了一个指针。如果作为实参传入的数组与原来形参的数组兼容,那么该实参的指针类型将与形参改写的指针匹配。这对VLA或是普通数组都是一样的。一个稍微令人吃惊的事情是,一维的VLA形参在改写以后变成了寻常的旧指针。考虑:
 void f(int n, int a[n])
after the compiler automatically rewrites the function, it becomes:

在编译器自动改写这个函数以后,它变成了:
void f(int n, int *a)
However, multiple dimensional VLA parameters become pointers to VLAs after the rewrite. For example,
然而,多维VLA形参在改写后变成了指向VLA的指针。例如,
void g(int n, int a[n][n+1])
becomes:
变成了:
 void g(int n, int (*a)[n+1])
Of course, multiple dimensional normal array parameters become pointers to normal arrays. It is in this context that most C programmers have used pointers to arrays without realizing it. The act of passing an “array” argument to an “array” function parameter is really a form of pointer assignment and works as described in the previous section. Thus you can pass either a normal array or a VLA to a function whose parameter is a normal array. You can also pass either a normal array or a VLA to a function whose parameter is a VLA. Listing 6 shows a function that sets the diagonal of its square array parameter to one and sets all other elements to zero. This function can be called on any n by n array of ints since the bounds of the array is passed as an argument. It is fairly common for the bounds of VLA parameters to be another parameter to the same function as in Listing 6, but this is not required. The run-time expression that is the bounds of the VLA may be any expression involving any variables or functions that are in scope at the time the parameter is declared. The bounds expression is evaluated each time the function is called since calling the function causes its parameter’s declarations to be executed, and the lifetime of the parameters ends when the function returns. Function prototypes with VLA parameters can be written just like the function definition. For example, a prototype for the function in Listing 6 could be:

    当然,多维普通数组形参变成指向普通数组的指针。在这个上下文中大部分C程序员都使用过指向数组的指针但是却没有注意到它。给一个“数组”函数形参传递一个“数组”参数实际上是一种指针赋值的形式,并如上面一节描述的那样工作。因此你可以给一个形参为普通数组的参数传入一个普通数组或是一个VLA。你还可以给一个形参为VLA的函数传入一个普通数组或是一个VLA。 Listing 6展示了一个函数,它把方形数组形参的对角线设为零,所有其他元素设为一。该函数可以在任一 n 乘 n 的 int 数组上调用,因为数组的边界是作为参数传递的。如Listing 6 中VLA形参的边界是同一个函数中的另一个形参,这很常见的但并不是必需的。VLA边界的运行时表达式可以是任意一个涉及声明形参时作用域内的任何变量或函数的表达式。边界表达式在每一次调用函数时求职,因为调用函数会执行它的形参声明,并且该形参的生存期在函数返回时结束。带VLA形参的函数原型可以正好如同函数定义那样书写。例如,为Listing 6中函数的原型可以是:
 void diag(int n, int a[n][n]);
There is an advantage to writing the prototype that way since it makes clear the relationship between the parameter n and the bounds of a. However, the bounds expression is not really needed for the prototype, and sometimes the bounds expressions might be complex or reference identifiers only in scope at the point of the function definition. Because of this, the bounds expression of a VLA in a function prototype may be replaced with a “*” character. In this context, the asterisk is just a placeholder for a run-time expression that will appear in the function definition. Thus, the function prototype for the function in Listing 6 can also be written as:

以这种方式书写原型有一个好处,它是的形参 n 和 a 的边界之间的关系变得清晰。然而,对原型来说边界表达式不是必需的,有时候边界表达式可能是复合的或是一个只在函数定义点作用域内的引用标识符。因为这一点,函数原型中VLA的边界可以用一个“*”字符代替。在这个上下文中,这个星号只是一个将会出现在函数定义中的运行表达式的占位符。因此,Listing 6 中的函数原型还可以这样书写:
void diag(int n, int a[*][*]);

As far as the compiler is concerned, the two prototypes for diag given above are identical.


对编译器来说,上面给出的两个 diag 原型是完全相同的。

Limitations on Pointers to VLAs

指向VLA的指针的限制

Like VLAs, pointers to VLAs cannot appear at file scope. They must be either parameters to function prototypes or local variables of a block. (The C Standard considers a function’s parameters to be locals of the block that is the function body.)


    如果VLA,指向VLA的指针不能出现在文件作用域内。它们必须是函数原型的形参,或是语句块的局部变量(C标准认为函数形参是函数体块的局部)。


Pointers to VLAs may not be static or extern. Such objects have a lifetime that starts before main is called and ends when the program exits. Since the size information for a VLA or pointer to VLA is fixed during its lifetime, such objects would have a size fixed during the running of the program. That sort of takes the variable out of variable length.


    指向VLA的指针不能是 static 或是 extern 的。这样的对象的生存期始于调用main之前并在程序退出时结束。因为一个VLA或是指向VLA的指针的大小信息在它的生存期内是固定的,这样的对象将在程序的运行时拥有固定的大小。这使得这些变量有点失去了变长的特性。

References

[1] Randy Meyers. “The New C: Why Variable Length Arrays?” C/C++ Users Journal, October 2001.

[2] Randy Meyers. “The New C: Variable Length Arrays, Part 2,” C/C++ Users Journal, December 2001.

[3] Randy Meyers. “The New C: Declarations and Initializations,” C/C++ Users Journal, April 2001, <www.cuj.com/reference/articles/2001/0104/0104d/0104d.htm>.


Randy Meyers is a 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。

Listing 1: Using pointers to arrays

列表1:使用指向数组的指针

void ex1()
{
    int i;
    int a[3];
    int (*pa)[3] = &a;

    for (i = 0; i < 3; ++i)
        (*pa)[i] = 1;

    // Save the result of calling f()
    // so the bounds of vla, pvla, and
    // the loop will be consistent even
    // if f() returns a different value
    // each time it is called
    // 保存调用f() 的结果,那么vla、pvla的边界,以及循环将是一致的,即使每一次调用f()都会返回一个不同的值
    int bounds = f();
    int vla[bounds];
    int (*pvla)[bounds] = &vla;

    for (i = 0; i < bounds; ++i)
        (*pvla)[i] = 1;
}
— End of Listing —

Listing 2: Using pointers to int to process arrays of int

列表2:使用指向int的指针处理int数组

void ex2()
{
    int i;
    int a[3];
    int *p1 = a;

    for (i = 0; i < 3; ++i)
        p1[i] = 1;

    // Save the result of calling f()
    // so the bounds of vla and
    // the loop will be consistent
    // 保存调用f() 的结果,那么vla、pvla的边界,以及循环将是一致的
    int bounds = f();
    int vla[bounds];
    int *p2 = vla;

    for (i = 0; i < bounds; ++i)
        p2[i] = 1;
    }
— End of Listing —

Listing 3: Using pointers to arrays to process two-dimensional arrays

列表3:使用指向数组的指针处理二维数组

void ex3()
{
    int i, j;
    int a[3][3];
    int (*pa)[3] = a;

    for (i = 0; i < 3; ++i)
        for (j = 0; j < 3; ++j)
            pa[i][j] = 1;

    // Save the result of calling f()
    // so the bounds of vla, pvla, and
    // the loop will be consistent
    // 保存调用f() 的结果,那么vla、pvla的边界,以及循环将是一致的
    int bounds = f();
    int vla[3][bounds];
    int (*pvla)[bounds] = vla;

    for (i = 0; i < 3; ++i)
        for (j = 0; j < bounds; ++j)
            pvla[i][j] = 1;
}
— End of Listing —

Listing 4: sizeof VLA through a pointer

列表4:通过指针求sizeof VAL

#include <stdio.h>

int main()
{
    int n = 10;

    for (int i = 0; i < 3; ++i) {
        char (*pvla)[n];
        n += 10;
        printf("%zu ", sizeof *pvla);
    }

    return 0;
}
— End of Listing —

Listing 5: Four combinations of assignments between pointers to (normal or variable length)arrays

列表5:四个指向(普通或变长)数组的指针之间赋值的组合

void ex5(int n)
{
    int a[10];
    int vla[n];
    int (*pa)[10];
    int (*pvla)[n];

    pa = &a;
    pa = &vla;
    pvla = &a;
    pvla = &vla;
}
— End of Listing —

Listing 6: VLA function parameter

列表6:VLA函数形参

void diag(int n, int a[n][n])
{
    int i, j;
    for (i = 0; i < n; ++i)
        for (j = 0; j < n; ++j)
            a[i][j] = i == j ? 1 : 0;
}
— End of Listing —

原文地址

http://www.drdobbs.com/cpp/184401476