Why Does C99 Complain About Storage Sizes

Why does C99 complain about storage sizes?

It's a chain of consequences of preprocessing and GNU C vs C99.

First up, net/if.h:

  1. net/if.h includes features.h
  2. Later on, it defines struct ifreq inside a #ifdef __USE_MISC block.

So:

  1. What is __USE_MISC? -- it is stuff common to BSD and System V
  2. Is it defined at this point? -- We need to check that out in features.h

So now, features.h:

  1. When you use --std=c99 GCC by default defines __STRICT_ANSI__ (since thats what C99 is)
  2. While preprocessing features.h, when __STRICT_ANSI__ is on, the BSD and System V features don't kick in. i.e. __USE_MISC is left undefined.

Back up to net/if.h: struct ifreq does not even exist after preprocessing! Therefore, the complaint about storage size.

You can catch the whole story by doing:

vimdiff <(cpp test.c --std=c99 -dD) <(cpp test.c --std=gnu99 -dD)

or diff'ing them in any other way (like diff --side-by-side) instead of vimdiff.

If you want this to cleanly compile with -std=c99, you must consider the inclusion of the _DEFAULT_SOURCE feature test macro (for glibc versions >= 2.19; for older glibc versions, use either _BSD_SOURCE or _SVID_SOURCE) so that the required functionality is enabled on top of what is offered by C99.

struct storage size error not reported by GCC with optimization turned on

When it appears at file scope, this code:

static struct x x;

declares x as an object of type struct x and having internal linkage. The type struct x need not have previously been declared:

If a type specifier of the form struct-or-union identifier occurs other than [in a struct type declaration], and no other declaration of the identifier as a tag is visible, then it declares an incomplete structure or union type, and declares the identifier as the tag of that type.

(C99 6.7.2.3/8)

A structure or union type that is incomplete when first encountered in a translation unit can nevertheless be completed later in that translation unit:

A structure or union type of unknown content [...] is an incomplete type. It is completed, for all declarations of that type, by declaring the same structure or union tag with its defining content later in the same scope.

(C99 6.2.5/22)

C discriminates between "declarations" generally, which specify the type of an object, and "definitions", which both specify the type and cause storage to be reserved. In a given translation unit, declarations that are not definitions may have incomplete type that is never completed within that unit.

Section 6.9.2 of C99 specifies which file-scope object declarations are definitions:

  • a declaration with file scope and an initializer is a definition (specifically, an "external definition", even though the object it defines may have internal linkage)
  • declaration with file scope and no initializer, and having static storage class or no storage class specifier is a "tentative definition". If the translation unit contains no external definition of the same object, then behavior is as if there were an external definition having initializer 0.

This section also specifies that "If the declaration of an identifier for an object is a tentative definition and has internal linkage, the declared type shall not be an incomplete type." That applies here, but since a type that is incomplete at one point in the translation unit can be completed by a later declaration, it does not inherently invalidate the line of code in question. However, it does invalidate a translation unit containing only that line.

So what's up with GCC?

Inasmuch as the code given, taken as a complete translation unit, violates a "shall not" language constraint, it yields undefined behavior. GCC is not obligated to reject the code or produce any kind of diagnostic, though it is permitted to do either or both. As it turns out, regardless of optimization level, gcc does issue a warning about the code if the -Wall or -pedantic flag is turned on. Any way around, gcc explicitly disclaims being a tool for validating standard compliance of your code.

This particular violation is relatively benign, however. Because the object is never referenced, gcc can pretend it was never even declared without risking wrong or unintended behavior.

The -fdce (dead code elimintation) option seems to be one that affects this particular behavior. Oddly, although -fdce is enough to suppress the error at optimization level O0, -fno-dce does not bring it back at higher optimization levels.

Why can't the size of a static array be made variable?

Since the size of the array you declare is not constant, what you have is an Variable Length Array(VLA). VLA are allowed by the c99 standard but there are some limitations associated with it. You cannot have an variable length array with static or extern storage class specifier.

You have an VLA with static storage specification and it is not allowed by the C99 Standard.

Reference:

c99 Standard: 6.7.5.2/8

EXAMPLE 4 All declarations of variably modified (VM) types have to be at either block scope or
function prototype scope. Array objects declared with the static or extern storage class specifier cannot have a variable length array (VLA) type. However, an object declared with the static storage class specifier can have a VM type (that is, a pointer to a VLA type). Finally, all identifiers declared with a VM type have to be ordinary identifiers and cannot, therefore, be members of structures or unions.

So if you want a dynamic size array with static storage specifier you will have to use a dynamic array allocated on heap.

#define MAX_SIZE 256
static int* gArr;
gArr = malloc(MAX_SIZE * sizeof(int));

EDIT:
To answer your updated question:

When you remove the static keyword from the declaration, the storage specifier of the declared array changes from static to global, note the standard quote above, it clearly mentions the restriction that VLAs are not allowed with static and extern storage specification. Clearly, you are allowed to have an VLA with global storage specification, which is what you have once you remove the static keyword.

static struct initialization in c99

C language relies on an exact definition of what is constant expression. Just because something looks "known at compile time" does not mean that it satisfies the formal definition of constant expression.

C language does not define the constant expressions of non-scalar types. It allows implementations to introduce their own kinds of constant expressions, but the one defined by the standard are restricted to scalar types only.

In other words, C language does not define the concept of constant expression for your type struct Test. Any value of struct Test is not a constant. Your compound literal (struct Test) {1} is not a constant (and is not a string literal) and, for this reason, it cannot be used as an initializer for objects with static storage duration. Adding a const qualifier to it will not change anything since in C const qualifier has no relation whatsoever to the concept of constant expression. It will never make any difference in such contexts.

Note that your first variant does not involve a compound literal at all. It uses a raw { ... } initializer syntax with constant expressions inside. This is explicitly allowed for objects with static storage duration.

So, in the most restrictive sense, the initialization with a compound literal is illegal, while the initialization with ordinary { ... } initializer is fine. Some compilers might accept compound literal initialization as an extension. (By extending the concept of constant expression or by taking some other extension path. Consult compiler documentation to figure out why it compiles.)

C fixed size array treated as variable size

According to the publicly available C99 draft standard n1256, the syntax for array declaration is described by

6.7.5.2 Array declarators

2

An ordinary identifier (as defined in 6.2.3) that has a variably modified type shall have
either block scope and no linkage or function prototype scope. If an identifier is declared
to be an object with static storage duration, it shall not have a variable length array type.


4

If the size is not present, the array type is an incomplete type. If the size is * instead of being an
expression, the array type is a variable length array type of
unspecified size, which can only be used in declarations with function
prototype scope; 124) such arrays are nonetheless complete types. If
the size is an integer constant expression and the element type has a
known constant size, the array type is not a variable length array
type; otherwise, the array type is a variable length array type.

So the expression in the [] must be an integer constant expression for the array to be declarable with static storage duration. The standard has this to say about integer constant expressions:

6.6 Constant expressions

6

An integer constant expression 99) shall have integer type and shall
only have operands that are integer constants, enumeration constants,
character constants, sizeof expressions whose results are integer
constants, and floating constants that are the immediate operands of
casts. Cast operators in an integer constant expression shall only
convert arithmetic types to integer types, except as part of an
operand to the sizeof operator.

Unfortunately, (unsigned)(2 / 0.5) does not apply the cast immediately to a floating-point constant, but rather to an arithmetic constant expression. This does not constitute an integer constant expression, and is thus not permissible as the size of an array with static storage duration.

Are there any size limitations for C structures?

From the C standard:

5.2.4.1 Translation limits

1 The implementation shall be able to translate and execute at least
one program that contains at least one instance of every one of the
following limits:

...
— 65535 bytes in an object (in a hosted environment only)

...
— 1023 members in a single structure or union

...
— 63 levels of nested structure or union definitions in a single
struct-declaration-list
...
13) Implementations should avoid imposing fixed translation limits
whenever possible.

Other than that, the upper bound is SIZE_MAX (maximum value for size_t).

C compile error: Variable-sized object may not be initialized

I am assuming that you are using a C99 compiler (with support for dynamically sized arrays). The problem in your code is that at the time when the compilers sees your variable declaration it cannot know how many elements there are in the array (I am also assuming here, from the compiler error that length is not a compile time constant).

You must manually initialize that array:

int boardAux[length][length];
memset( boardAux, 0, length*length*sizeof(int) );

cannot acces struct ifreq

As stated here, you should use -std=gnu99 instead of -std=c99 when compiling.

Is there a way to check if variable length arrays are created on stack / heap?

the array size seems too big to fit into stack, but it never complains.

By "never complains", I presume you mean that the program doesn't crash.

You never touch the memory that you allocate and the compiler was smart enough to prove it and didn't allocate anything.

Let us take the address of the variable, and send it to a function that is defined elsewhere:

int array[valone][valtwo] = {};
cout << &array << endl;

Now, the compiler wasn't quite so sure that the array is never accessed. That's because it can't go into the streaming operator which implemented in another translation unit. Perhaps the operator will dereference the pointer; we must make sure that the array exists.

Segfault crashed this program on my first attempt. The stack was overflown.


I suppose this kind of crash test is a way to test if VLA is on the stack.

Mikhail's suggestion in comments to compare adjacency of automatic variables to the VLA is a decent platform dependant idea, but it can only work if you allocate small enough VLA that it doesn't crash the program.



Related Topics



Leave a reply



Submit