Home
Articles
FAQ
Pointers and Arrays
by

Vijay Kumar R Zanvar
(assisted by Jayasimha Ananth)
Contents
---------------------------------------------------------

Preface


Chapter 1 - Objects and Storage Allocation
    1.1 Getting Started
    1.2 What is a Pointer?


Chapter 2 - Pointers and Functions
    2.1 Parameter Passing Techniques
    2.2 Pointers as Function Parameters
        2.2.1 Simple Usage
        2.2.2 Qualified Usage
            2.2.2.1 Using `const' Qualifier
            2.2.2.2 Using `volatile' Qualifier
            2.2.2.3 Using `restrict' Qualifier
        2.2.3 `static' Storage Class Usage
        2.2.4 Generic Usage


Chapter 3 - Pointers and Arrays
    3.1 One-dimensional Array
    3.2 How One-dimensional Array is Stored in Memory
    3.3 Operations on Array
        3.3.1 Finding the Size of an Array
        3.3.2 Finding the Address of an Array Object
        3.3.3 An Array Variable is Not a Modifiable Lvalue
        3.3.4 Obtaining a Pointer to the First Element of an Array
        3.3.5 Accessing Array Elements
    3.4 Variable Length Array



Preface

This document is the first part of "Pointers and Arrays", owing to the length it has grown to. Following are few basic informations about this article:

* sizeof(int) is assumed to be 4 bytes through out the document. In C, sizeof(char) is always one byte irrespective of the value of CHAR_BIT (defined in <limits.h>)

* I guess this document could be of some help to a novice programmer.



Chapter 1 - Objects and Storage Allocation

1.1 Getting Started

The basic unit of data, of a running program, is an object; and if the program were written in C, then the sizes of objects would vary according to their data types.

So, what is an object? An object can be defined as:

"An object is a contiguous block of memory forming a single logical data structure. Objects are the units of allocation, deallocation, etc., and has a well-defined set of operations."

Note: We use the term `object' in a sense introduced by K&R in the section A.5, Objects and Lvalues, which has no relation with Object Oriented Programming.

The C programming language offers various data types to suit different purposes. They can be broadly categorized as follows:

+ Integral types (char, short, int, long, long long, enum, _Bool) (long long and _Bool are new feature of C99)

+ Real floating types (float, double, long double)

+ Complex floating types (float _Complex, double _Complex, long double _Complex) (New feature of C99)

+ void is an empty set of values

+ Derived types (structure type, union type, array type, pointer type, function type)

+ Incomplete type (array of unknown size, a structure or union of unknown content)

Real and complex floating types are collectively know as floating types; integral and floating types are collectively called arithmetic types; arithmetic and pointer types are collectively called scalar types; arrays and structures are collectively called aggregate types.

As per the definition, an object type has a well-defined set of operations that can be applied upon it. For example, the C language does not allow bit-wise operation on floating types, multiplication on pointer types, and cast operation on lvalues, etc.

For the forthcoming topic, let us build a ground by the following discussion:

int k; /* line 1 */
k = 2; /* line 2 */

The statement of line 1 is a declaration. It is an information to the compiler that the name of this integer object, if defined, would be `k'. It is instructive to note that this statement is only a declaration, and not a definition of k. An implementation is free not to allocate storage for the object when it encounters it's declaration.

The statement of line 2 is the definition of the integer object that was declared in the line 1. A definition, in simpler terms, is an allocation of storage; and, an initialization of the object, if required. But, why so? A compiler may not see it fit to allocate storage at the declaration time, since the object may remain unused (as warned by the compiler message: "`ident' declared, but not used"). When it finds an actual usage -- k = 2; here -- it allocates storage.

On the other hand, the following statement is, both, a declaration as well as a definition:

int i = 1;

In the following example, observe the usage of j:

int k, j; /* line 1 */
j = 5; /* line 2*/
k = j; /* line 3*/

The compiler treats the variable j in two different ways: in the line 2, j is the address of the integer object; and in the line 3, it is the value of that object! Technically, j is an lvalue in the line 2; and, a rvalue in the line 3.

An lvalue can be defined as:

"An address in the memory that is the location of an object whose contents can be modified."

On the other hand, an rvalue can be defined as:

"A value that does not necessarily have any storage address. An lvalue can be converted to an rvalue, but _not_ the other way around."

The C Standard introduces another term "modifiable lvalue". Section 3.3.3 talks about modifiable lvalue.



1.2 What is a Pointer?

C is a language of "expressions", for it offers the facility to explore, the practicability to implement, and the depth of low-levelness. Pointer is one such concept that can offer an approach to more compact and efficient code.

A pointer, in its simplest form, is an object capable of holding an lvalue of a compatible object. In C, the address-of operator (&) gives the address of an object. The following example shows it how:

int k;
int *p; /* p is pointer to an int */
p = &k; /* p points to k */
*p = 3; /* k is now 3 */

The second statement declares p as a pointer to an integer. The address-of operator gives the address of k which is stored in the pointer p; p is now said to point to k. The asterisk(*) in the expression, *p, is called a dereference operator which retrieves the value pointed by the pointer. Now, *p can occur in any context or expression where k could.

Pictorially,

address:   0x1000                       0x2000
         __________    k              __________ 
        |    3     | <----           |  0x1000  |
         ----------       |           ----------
                          |              | p
                          |______________|
                              points to

Since, both, p and k are objects, an implementation-defined memory storage unit is allocated to each as shown. The objects k and p reside at the addresses 0x1000 and 0x2000, respectively; and, p is shown pointing to k.



Chapter 2 - Pointers and Functions

Functions provide modularity to the program design. Breaking a program into modules makes program implementation and understanding easier. A function should be designed to perform only one task, as shown in the following example:

char *
str_cpy ( char * s1, const char * s2 )
{
    char *s = s1;
            
    while ( (*s1++ = *s2++) )
        ;
    return s;
}

This function copies the string pointed by s2 to s1.

Under certain circumstances, pointers offer an easy logic when used with functions. However, there are few concepts that deserve explanation. Following subsections illuminate various aspects related to pointers and functions.

See also sections: 2.2.2.3 (for restrict-qualified usage).



2.1 Parameter Passing Techniques

The following example illustrates the terminologies used in this document. The comments show who is who.

#incluce <stdlib.h>

int                         /* int is the return type           */
mango ( int seed )          /* mango() is the called-function   */
{                           /* seed is a parameter              */
    int result;
    
    /* function logic here */

    return result;          /* result is the the return value   */
}

int                         /* int is the return type           */
main ( void )               /* main() the calling-function       */
{
    mango ( 10 );           /* 10 is the argument               */
    return EXIT_SUCCESS;    /* EXIT_SUCCESS is the return value */
}

Functions, accepting one or more parameters, expect them to be passed. In C, the only true method of parameter passing is known as "call by value". The following example demonstrates the last statement:

#include <stdlib.h>

void
raisin ( float x ) { /* process x here */ }

void
grape ( float *xp ) { /* process xp here */ }

int
main ( void )
{
    float f;
    float *fp = &f;

    /* ... */
    raisin ( f );
    grape ( fp );
    return EXIT_SUCCESS;
}

When the declaration of an argument (f and fp) matches with the declaration of the corresponding parameter (x and xp), then the argument has been passed by value. And, when an argument is passed by value, any modification the called-function makes to the parameter does not reflect in the calling-function. So, any modification to xp does not reflect in fp, however, any modification to *xp reflects in f.



2.2 Pointers as Function Parameters

At times, the called-function is required to modify one or more parameters so that they can reflect in the calling-function. In such situations, we use pointers; that is, we pass pointer to the object whose value requires to be changed. However, keep in mind that the pointer, itself, is passed by value (section 2.1)! Only the object pointed by the pointer can be changed, any change to the pointer is not reflected in the calling-function.

Pointers are an interesting concept which can become fun when understood, or can become nervy for the programmer if ill-understood. However, there are different schema or styles of using pointer parameters depending upon the intent of usage. Following subsections spell out pointer usage.



2.2.1 Simple Usage

In the section 1.2, What is a Pointer?, we discussed pointer semantics. Here, in this subsection, we discuss the simplest use of pointer as a function parameter.

In it's basic or simplest form, a pointer parameter has the following template:

return_type function_name ( type *ptr );

and, has following usage pattern:

return_type 
function_name ( type *ptr )
{
    type    var;
    type    *new_ptr;
    
    new_ptr = ...;  /* make new_ptr to point to valid
                     * type and location */
    var = ...;      /* compute `var' */ 

    *ptr = var;     /* let the calling-function know 
                     * the value of `var' */
    ptr = new_ptr;  /* allowed, but not reflected;
                     * this usage is discouraged */

    /* 
     * depending on the type of `return_type', return
     * a value of appropriate type
     */
}

This pattern is generally used to

+ pass an aggregate type object since passing them by value is not efficient.

+ pass an object whose value need to modified in some way.

The parameters can also be attributed by the `register' storage class in this and the following usages.

See also: 2.2.2.1 (since simple and const-qualified usage are more or less the same)



2.2.2 Qualified Usage

The foundational attribute of an object is decided by it's data type. Additional attributes, if any, are influenced by the object's type specifier (signed or unsigned), type qualifier (const, volatile and/or restrict) and storage class (auto, register, extern, or static).

The singularity of a parameter can be broadened by

+ using either of type specifiers: signed or unsigned. This attribute affects the range of values that an object can have.

+ a type qualifier (to be discussed).

+ register storage class indicating that the compiler, if possible, should place the parameter in a register for fast access. Usage of `static' storage class are discussed in 2.2.3.



2.2.2.1 Using `const' Qualifier

An argument that is not be modified by the called-function is, often, passed by value. This is generally the case when the argument is of the basic type. However, when the argument is of an aggregate type, matters are little different in terms of efficiency (execution time and space).

The function template, under this heading, looks like this:

return_type function_name ( const type *ptr ); 

Truly speaking, the object pointed by ptr is not really a constant, but a read-only object. A constant in C is an absolute numeric value, like 2. It is an implementation-defined result if an effort is made to change the object pointed by the pointer, ptr.

Consider an example to compare two structure variables for equality.

int
struct_equal ( struct mango a, struct mango b )
{
    if ( .. )
        return 1;   /* equal */
    else
        return 0;   /* not equal */
} 

Let the sizeof(struct mango) be, say, 64 bytes. If the implementation uses a stack to pass the parameters, then 128 (64+64) bytes need to be pushed and popped, which is expensive in terms of operation and space.

Instead, if we change the function definition to

int
struct_equal ( const struct mango *a, const struct mango *b )
{
    if ( .. )
        return 1;   /* equal */
    else
        return 0;   /* not equal */
}

only the addresses of the two structure variables need to be passed. Typically, the sizeof(a) is 4 bytes on a 32-bit machine. Now, structure members can be accessed through the pointers. The advantage of using pointers can now be noticed. The `const' qualifier helps the compiler to produce diagnostics if an attempt were made to modify the object pointed by either a or b.

See also sections: 1.1 (for aggregate type).

2.2.2.2 Using `volatile' Qualifier

Using volatile-qualified parameter(s) is not a common practice. However, there is, at least, one situation where the local variables (parameters are also local) must be made volatile-qualified in order to avoid undefined behavior.

That is, when a function parameter or any other local variable of a function is a subject of modification between a call to setjmp() macro and the corresponding call to longjmp() function. Consider a hypothetical situation as shown in the following function:

void
function_name ( volatile type *ptr )
{
    volatile int   vi;
    extern jmp_buf jbuf;

    /* code to initialize vi */
    
    if ( setjmp ( jbuf ) )
    {
        /* 
         * return from longjmp().  Here, ptr and vi will have 
         * defined values since they are volatile-qualified.
         */
    }
    else
    {
        /* 
         * return from setjmp()
         */
    }

    /* code to modify vi and ptr */
} 
    
    (Thanks to Christian Bau)

2.2.2.3 Using `restrict' Qualifier

`restrict' qualifier is an invention of the C99 committee. The object which is accessed through the restrict-qualified pointer has a special relation with that pointer. Only the pointer types can be restrict-qualified.

A restrict-qualified pointer, that is a function parameter, is the sole means of access to an object. Following is a reproduction of my question, on restrict qualifier usage, posted to comp.lang.c to which Arthur J. O'Dwyer has replied thus:

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
On Tue, 27 Apr 2004, Vijay Kumar R Zanvar wrote:
>
> Are the following inferences of mine correct?
>
> 1.  #include 
>     char *strcpy(char * restrict s1,
>         const char * restrict s2);
>
>     a.  s1 != s2

  Not necessarily.  Yes, if we're to assume that when you
wrote 'strcpy' above you really meant 'strcpy', the function
that copies the target of 's2' into the target of 's1'.  But
generally, no, we could have the pathological case

    int ptreq(const char * restrict s1, const char * restrict s2)
    {
        return (s1 == s2);
    }

that would be perfectly conforming and unable to generate
undefined behavior, no matter the values of 's1' and 's2'.

>     b.  That means,
>
>             char s[10] = "vijoeyz";
>             (void) strcpy ( s, s ); /* though useless */
>
>         is not allowed!

  Naturally.  In the case of 'strcpy', the Standard explicitly
rules this sort of thing out (see 7.21.2.3 #2).  But
again, in the general case, you *could* pass 's' twice to the
'ptrcmp' function I defined above, because that function never
tries to access the targets of those pointers --- only their
values.

> 2.  #include 
>     size_t strlen(const char *s);
>
>     a.  If there is only one parameter of type "pointer to T", for any type
>         T, then the `restrict' qualifier need not be used.

  No.  'restrict' guarantees that the pointer will have a sort of
a "lock" on its target for its entire lifetime.  This means that
the following program exhibits undefined behavior:

    char g[] = "hello world";
    void foo(const char * restrict s) {
        g[0] = 'J';
        puts(s);
    }
    int main(void) {
        foo(g);
    }

because the target of 's' is modified through 'g' during the
lifetime of 's'.  I'm almost positive that by writing

    char ga[] = "hello world";
    char * restrict g = ga;

at file scope, you would be "preventing" anyone's tampering with
that string except through 'g', for the entire run of the program.
Quotes around "preventing" because it's not something the compiler
can catch; it just means anyone who tampers with that array invokes
undefined behavior on himself.


  N.B.: You can't define

    char restrict ga[] = "hello world";

by itself; 'restrict' can only be applied to pointers.  I don't see 
why it can't apply to array objects too; perhaps someone will enlighten 
me.

-Arthur
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 

Read the complete thread here

2.2.3 `static' Storage Class Usage

This is a new feature of C99. It allows a compiler to generate code which can run faster on machines. The `static' storage class specifier can appear inside [ and ] of an array declarator of function parameters. For example, in the function:

void function_name ( char s[static 5], char t[static 5] );

the static specifier guarantees that at least 5 elements pointed by the pointers, s and t each, are available. Hence, the compiler can generate code to pre-fetch the 5 elements in advance. Functionally, it is equivalent to:

void function_name ( char s[5], char t[5] );

But, it is not guaranteed that s and t, in the above declaration, will point to disjoint objects. On the other hand, the following declaration says that s and t point to disjoint (array) objects containing, at least, 5 character elements:

void function_name ( char s[restrict static 5], char t[restrict static 5] );

In addition to the above information, this usage also guarantees that the pointers are not NULL, and point to appropriate objects.

2.2.4 Generic Usage

A pointer to void, void*, is a generic pointer. A generic pointer is capable of pointing to any object (except for bit-fields, objects declared with the register storage class and functions). A normal pointer can be converted to a generic pointer and vice versa, without the loss of information.

Functions using generic pointers implement common utility for almost all objects of various type. One such function is the standard memcmp() function which compares the objects pointed by s1 and s2, in the below declaration, for equality. It's prototype is:

#include 
int memcmp(const void *s1, const void *s2, size_t n);

Internally, it casts s1 and s2 to pointers to character (signed or unsigned), and compares character-by-character till either of the following conditions occurs:

+ characters pointed by character pointers are not equal; returns with a nonzero value.

+ all n character pointed by internal character pointers have been compared to be equal; returns with 0.

Chapter 3 - Pointers and Arrays

Pointers and arrays are inseparably related, but they are not the synonyms. In this section we look into one-dimensional array, it's storage pattern, how the array elements are accessed. And lastly, we look into a new feature of C99, variable length array.

3.1 One-dimensional Array

An array is a nonempty set of sequentially indexed elements having the same type of data. Each element of an array has a unique identifying index number. Changes made to one element of an array do not affect the other elements.

In C, the declaration

int a[10];

defines an array of size 10, each of which is an integer object. These objects -- named a[0], a[1], ..., a[9] -- appear contiguously in the storage area, that is, there is no padding between the array members.

An array type is called as an aggregate type since it is considered to be derived from it's element type. If the element type is T, then the array type is called as "array of T". For example, in the above declaration element type of a is integer, so the array a is called as "array of integers".

An array must not be declared/defined with the `register' storage class specifier; doing so is an undefined-behavior.

3.2 How One-dimensional Array is Stored in Memory

The reader might have noticed that, in C, the rightmost subscript of a two-dimensional array varies faster than the leftmost (in fact, there are no multidimensional arrays in C, but array of arrays). This fact suggests that the array is stored in a "row major addressing" format. However, for a one-dimensional array the case is the simplest: The array occupies a contiguous block of memory. The array a (declared in Subsection 3.1) is laid out in memory as a contiguous block, as shown below:

          _____ _____ _____ _____ _____ _____ _____ _____ _____ _____
      a: |     |     |     |     |     |     |     |     |     |     |
          ----- ----- ----- ----- ----- ----- ----- ----- ----- -----
          a[0]  a[1]  a[2]  a[3]  a[4]  a[5]  a[6]  a[7]  a[8]  a[9]

Elements of array are stored in the successive increasing locations of memory. For example, if the array starts at memory location 0x1000, then with our assumed size of an integer, the first element is stored at location 0x1000, the second element at location 0x1004, and so on.

3.3 Operations on Array

Broadly speaking, there are only three operations that can be applied on an object of an array type! They are summarized as follows:

+ Finding the size of the array object using the sizeof operator

+ Finding the address of the array object using the address-of operator

+ Obtaining a pointer to the initial element of the array

All other operations are usually a combination of these. Let us see, in some detail, each operation on array.

3.3.1 Finding the Size of an Array

The sizeof operator yields the size of it's operand in bytes. The operand is one of: an expression or the parenthesized name of a type. So, when applied to an operand that has the array type, the result is the total number of bytes in the array.

The following fragment of code finds the size of the array:

{
    int a[10];
    size_t size = sizeof a;  /* the size is 10 * sizeof(int),  */
                             /* which, in our case is 40 bytes.*/
} 

Consider another example in which a function named apple() takes a parameter declared to have array type.

void
apple ( int seeds[6] ) 
{ 
    size_t size;
    /* ... */ 
    size = sizeof seeds; /* the size is sizeof(int*). */
} 

Subsection 3.3.4 will show us that an array variable decays into pointer to it's first element, except under some situations as described later. So, when applied to a parameter declared to have array type, the sizeof operator yields the size of the pointer type; pointer to int in the above example.

Another use of the sizeof operator is to compute the number of elements in an array. The following expression finds that:

sizeof array / sizeof array[0]

The Section 3.4 talks also about finding the size of variable length arrays.

3.3.2 Finding the Address of an Array Object

The address-of (&) operator yields the address of it's operand. The result is generally interpreted as "a pointer to ...".

An example will make matters clear.

#include <stdio.h>

int 
main ( void )
{
    int a[10];
    printf ( "pointer to the first element: %p\n", (void*) a );
    printf ( "address of the array: %p\n", (void*) &a );
    return 0;
}

The output, upon running the above program, was:

pointer to the first element: 8f518
address of the array: 8f518 

which may be different from a system to another. Numerically, the values are same in both the cases; however, their interpretation is not. We defer the explanation on a stand-alone array variable, like a in "(void*)a", till the Subsection 3.3.4. Let us see how &a is interpreted.

As mentioned earlier, the result of &a -- i.e., the result of address-of operator -- is "a pointer to" the array of 10 integers. It is equivalent to saying,

int (*ptr)[10] = &a; 



3.3.3 An Array Variable is Not a Modifiable Lvalue

An lvalue designate an object in the memory; however, according to the specification, the object can not be modified using an lvalue. The standard requires an object to have a modifiable lvalue in order to modify it. Hence, a modifiable lvalue is an lvalue which can appear on the left hand side of the assignment operator.

An array, by definition, is not a modifiable lvalue; it's only an lvalue! This is pretty confusing. Owing to this reason, certain operations can not be applied to an array object. Following, for example, are few illegal operations on arrays:

{
    int a[10], b[10];
    /* ... */
    a = b; /* copy array b to array a */
    a++; /* point to element a[1] */
    (void)a + b; 
}



3.3.4 Obtaining a Pointer to the First Element of an Array

Let us begin with a simple example.

{
    int a, b; /* line 1 */
    int p[10]; /* line 2 */
    a = 3; /* line 3 */
    b = a; /* line 4 */
    (void)p; /* line 5 */
}

Every object of this example -- i.e., a, b and p -- has a value. In the line 4, the value of a is 3; whereas, the value of b is the address of the object (lvalue). The value of p, however, in the line 5, is not ten ints stored in that array! Instead, the value of p is a pointer to the first element of the array (See 3.3.2). Thus, p is the pointer to the first element of the array, as shown below.

{
    int p[10];
    int *ptr;

    ptr = p; /* ptr == &p[0] */
}

There is an exception, however, when the array variable is not a pointer to the first element. This is summarized by the following paragraph:

An expression of type "array of type" is converted to type "pointer to type" unless:

+ it is the operand of the sizeof operator, or

+ it is the operand of the address-of (&) operator, or

+ it is a string literal used to initialize an array.

(See examples from Subsections 3.3.1 and 3.3.2)

3.3.5 Accessing Array Elements

Pointers and arrays are inseparably related. Here is our point: Access to array elements involves pointers. The following example illustrates this fact.

The declaration,

int a[10];
int b;

defines an array 10 of integers.

By now, we know that "a" points to the first element of the array; and *a is it's value. So the assignment

b = *a;

copies the content of the first element. Similarly, to copy the second element we use

b = *(a+1);

Similarly, a+5 points to the sixth element of the array, and *(a+5) is it's content. This notion of array access is so common that the C language introduced the subscript operators ([]). This is the confusing fact for a novice programmer to understand. Remember, *(a+1) is equivalent to a[1]; in fact, it is the other way round: a[1] is always treated as *(a+1) by the compiler.



3.4 Variable Length Array

C99 introduced a new array type called as Variable Length Array, VLA for short. VLAs are execution time arrays. Prior to this introduction, an array declaration required the size expression of the array to be a compile-time constant. This restriction, however, has been waived off in C99.

A VLA is declared like any other automatic array, as follows:

type ident[ nonconst-expression ]; 

For example,

{
    int n = 5;
    /* ... */
    int array[n];       /* run time storage allocation of n elements */
    /* ... */           /* array is used here */ 
}                       /* deallocation when it's out of scope here */

The storage for the array is allocated at the point of declaration, and is deallocated when the block is exited. Note that at time of allocation, the value of n may or may not be 5, but is required to be a positive non-zero value.

PS: C99 allows declarations at the point-of-use.

{
    printf ( "Hello, world\n" );

    int n = 5;
    while ( n-- ) { /* .. */ }

    for ( int i = 0; i < 10; i++ )
        { /* .. */ }
}

VLAs can be used in function parameters, as shown:

void mango ( int m, int a[m] ) { /* .. */ } /* [1] */
void banana ( int b[n], int n ) { /* .. */ } /* [2] */

In example [1], a is an array of m number of integers. In example [2], this declaration of array could be an error since n is undeclared at the point of array declaration. This will work, however, if an external variable with name n exists, otherwise results in compilation error.

The prototypes involving variable array types use an asterisk (*) to indicate "variableness" of the array, as shown:

void mango ( int, int a[*] );               /* [3] */
void banana ( int m, int a[*][m] );         /* [4] */
void grape ( int m, int a[][*] );           /* [5] */
void apple ( int m, int a[][m] );           /* [6] */

These *'s indicate that the respective arrays are of unspecified size, but these would be available in the function definition. Hence, *'s can be used only in the function prototype scope.

There are, however, few restrictions on the usage of VLAs. They are summarized as follows:

+ VLAs can not have file scope, nor they have linkage.

extern int n;
int a[n];                        /* not allowed at file scope */
extern int b[n];                 /* extern linkage on VLAs not allowed */

void orange ( void ) { /* .. */ }

+ VLAs can not have static storage class (even inside block scope). A "pointer to VLA", however, can have static storage.

int e[10];
void citrus ( int n )
{
    static int a[n];             /* static storage not allowed */
    static (*ptr)[n] = &e;       /* compatible only if n == 10 */
    extern int b[n];             /* error: b has extern linkage */
/* .. */
}

+ VLAs can not be members of structures and unions, since the standard requires that only the ordinary identifiers can have variable array types.

extern int n;
struct color {
    int red[n];             /* error: VLA as struct members */
    int green[n];
    int blue[n];
    int (*ptr)[n];          /* error: pointer to VLA is not allowed */
};

+ Jumping out of the block will deallocate the array storage; whereas a jump into the block containing the VLAs is a constraint violation i.e., an error.

goto inside; /* this is not allowed */

{
    extern int n;
    int a[n];

    /* .. */
    inside:
    /* constraint violation */

    goto outside; /* conforming */

    /* .. */
}

outside: ;
/* valid C */ 

This document has been read or viewed Counter times.

1