Using sizeof() in array declarations in C89 - c

I was under the impression that variable-size array declarations were not possible in C89. But, when compiling with clang -ansi I am able to run the following code:
double array[] = { 0.0, 1.0, 2.0, 3.0, 4.0 };
double other_array[sizeof(array)] = { 0.0 };
What is going on here? Is that not considered a variable-size array declaration?

In ANSI C89 a.k.a. ISO C90, the sizeof operator yields an integer constant, which is suitable for array dimensions. Function calls, for example, are not.
I'd like to add another remark, since I believe the code as-is has a problem that might get overlooked.
If the other_array is declared as
double other_array[sizeof(array)];
it will neither have the same number of elements, nor the same size (that would only be true for array of char) as array[]. If the intent is to declare a second array with the same number of elements (regardless of type), use this:
double other_array[sizeof(array)/sizeof(*array)];

That is because result of sizeof operator is constant expression, so it does not qualify for VLA, just like the following declaration:
int other_array[5];
cannot be variable length array either. From C11 (N1570) §6.6/p6 Constant expressions (emphasis mine going forward):
An integer constant expression117) shall have integer type and shall
only have operands that are integer constants, enumeration constants,
character constants, sizeof expressions whose results are integer
constants, _Alignof expressions, and floating constants that are the
immediate operands of casts.
For sake of completeness, the sizeof operator does not always results into constant expression, though this only affects post-C89 standards (in C11 VLAs were made optional). Referring to §6.5.3.4/p2 The sizeof and _Alignof operators:
If the type of the operand is a variable length array type, the
operand is evaluated; otherwise, the operand is not evaluated and the
result is an integer constant.

First, let's see the criteria for an array (not being) a VLA. C11 doc, chapter §6.7.6.2,
[...] 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; [...]
Coming to your case, sizeof is a compile-time operator, so it produces a value that is considered compile time constant expression. An array definition, whose size is specified as a compile time constant expression is not a VLA. So, in your code,
int other_array[sizeof(array)]
is not a VLA.
Regarding the sizeof operator result, from C11, chapter §6.5.3.4, (emphasis mine)
The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. [...] otherwise, the operand is not evaluated and the result is an integer constant.

Related

using sizeof() to initiate an array

I just found out that I could use sizeof() to initiate an array but not strlen(), why is this?
char str[] = " ## Aab, ~bccdD>> e", str2[sizeof(str)]={-1};
if I use strlen(), it would give me this error, but shouldn't the return value of sizeof be a variable too?
error: variable-sized object may not be initialized
char str[] = " ## Aab, ~bccdD>> e", str2[strlen(str)]={-1};
sizeof is an operator, not a function, and with one exception its value is known at compile time. When used as an array size, it’s similar to using a constant expression like 10.
Since strlen is a function, it isn’t evaluated until run time. As of C99, you can declare an array with a runtime value as the size - these are called variable-length arrays. While useful, VLAs have some limitations, one of which is that you cannot declare them with an initializer. You would have to set the initial value using memset or something like that:
char str[] = " ## Aab, ~bccdD>> e", str2[sizeof(str)];
memset( str2, -1, sizeof str2 );
The one exception to sizeof being evaluated at runtime is when it is used on a VLA, since the size of a VLA isn’t established until runtime.
C 2018 6.7.9 says a variable length array may not be initialized:
The type of the entity to be initialized shall be an array of unknown size or a complete object type that is not a variable length array type.
C 2018 6.7.6.2 4 says that if an array is declared with a size that is not an integer constant expression, it is a variable length array:
… 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…
Constant expressions are limited by C 2018 6.6 3:
Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated.
Thus, using strlen(str) makes the array a variable length array, which you cannot initialize. I do not know the exact motivation for this prohibition, but I will note that allowing initialization of variable length arrays could require the compiler to generate more code than is typical for C definitions.
That suffices to explain the error message, but I will note that integer constant expressions are further limited by C 2018 6.6 4:
An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, _Alignof expressions, 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 or _Alignof operator.

Why const pointer difference cannot be used as initializer for a static variable?

Compiling following piece of C code (using MSVC):
char * const p1;
char * const p2;
static size_t sz = p2 - p1;
results in "initializer is not a constant" error for definition of sz.
As pointers are const (tried also with arrays, same error), why is pointer diff not constant?
Per C 2018 6.6 7, for constants used in initializers, the C standard only requires implementations to support an arithmetic constant expression, a null pointer constant, an address constant, or an address constant for a complete object type plus or minus an integer constant expression. None of these include the subtraction of two addresses, as shown by their definitions below.
A C implementation might be able to resolve the subtraction of two addresses of symbols of the same kind, especially if the compiler can see they will be placed in the same program segment, and the C standard permits an implementation to do this. However, the standard does not require a C implementation to support this, and that is at least in part because the subtraction of two symbols may involve various difficulties. One is that two symbols might refer to objects in different program segments, such as one in a constant read-only section and another in an uninitialized data section. The compiler could not know the relative difference between these sections because it depends on contributions from other object modules linked into the program, and the object module format might not support any way of expressing this difference as something to be resolved by the linker. Even within one section, some object module and symbol schemes may allow the linker to rearrange things, to optimize for alignment issues.
Per 6.6 8:
An arithmetic constant expression shall have arithmetic type and shall only have operands that are integer constants, floating constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, and _Alignof expressions. Cast operators in an arithmetic constant expression shall only convert arithmetic types to arithmetic types, except as part of an operand to a sizeof or _Alignof operator.
Per 6.6 9:
An address constant is a null pointer, a pointer to an lvalue designating an object of static storage duration, or a pointer to a function designator; it shall be created explicitly using the unary & operator or an integer constant cast to pointer type, or implicitly by the use of an expression of array or function type. The array-subscript [] and member-access . and -> operators, the address & and indirection * unary operators, and pointer casts may be used in the creation of an address constant, but the value of an object shall not be accessed by use of these operators.
Per 6.3.2.3 3:
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.…
Per 6.6 6:
An integer constant expression shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, _Alignof expressions, 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 or _Alignof operator.

array bound is not an integer constant before ']' token when it is actually constant

I am trying to have an array, which has a defined size known during compile time.
const uint8_t a[2] = {0, 127}; // Fine
const uint8_t aRange = a[1] - a[0]; // Fine
double sums[aRange]; //Fails
But this fails by gcc with
error: array bound is not an integer constant before ']' token.
As a workaround I intend to use macro variables. But would like to know if there is any logic behind it. There is this answer, which is most related. However, according to the answer, it should have worked.
aRange is a constant integer, but not an integer constant. Isn't English a fun language?
C11 §6.4.4.1 Integer constants
C11 §6.6 Constant expressions ¶6
An integer constant expression117) shall have integer type and shall only have operands that are integer constants, enumeration constants, character constants, sizeof expressions whose results are integer constants, _Alignof expressions, 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 or _Alignof operator.
117) An integer constant expression is required in a number of contexts such as the size of a bit-field member of a structure, the value of an enumeration constant, and the size of a non-variable length array. Further constraints that apply to the integer constant expressions used in conditional-inclusion preprocessing directives are discussed in 6.10.1.
C11 §6.7.6.2 Array declarators — one of the more inscrutable parts of the standard. (¶2 is a constraint; ¶4 and ¶5 specify the semantics of array declarators.)
¶2 If an identifier is declared as having a variably modified type, it shall be an ordinary identifier (as defined in 6.2.3), have no linkage, and have either block scope or function prototype scope. If an identifier is declared to be an object with static or thread 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 or type names with function prototype scope;143) 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. (Variable length arrays are a conditional feature that implementations need not support; see 6.10.8.3.)
143) Thus, * can be used only in function declarations that are not definitions (see 6.7.6.3).
¶5 If the size is an expression that is not an integer constant expression: if it occurs in a declaration at function prototype scope, it is treated as if it were replaced by *; otherwise, each time it is evaluated it shall have a value greater than zero. The size of each instance of a variable length array type does not change during its lifetime. Where a size expression is part of the operand of a sizeof operator and changing the value of the size expression would not affect the result of the operator, it is unspecified whether or not the size expression is evaluated.
At file (global) scope, you must have an integer constant expression for the dimensions of an array. In a local variable in C99 or later, what you've written would be OK for a VLA (variable-length array).
You could work around this with:
enum { A_MIN = 0, A_MAX = 127 };
const uint8_t a[2] = { A_MIN, A_MAX };
const uint8_t aRange = a[1] - a[0];
double sums[A_MAX - A_MIN];
In C, you can't write const uint8_t aRange = a[1] - a[0]; at file (global) scope, so your code should have been OK unless you're using an antiquated C compiler that doesn't recognize C99 or later (or it defines __STDC_NO_VLA__).
Jonathan's answer is accepted. As a workaround I used macros though.
#define A_MIN 0
#define A_MAX 127
const uint8_t a[2] = {A_MIN, A_MAX};
const uint8_t aRange = A_MAX - A_MIN;
double sums[aRange];

Not a constant initializer element?

I encountered a confusing case when I was doing semantic analysis for my compiler course.
#include <stdio.h>
int a = "abcd"[2];
int main()
{
char b = "abcd"[2];
printf("%d\n%c\n", a, b);
return 0;
}
GCC says "error: initializer element is not constant" for variable "a".
Why?
The C language requires initializers for global variables to be constant expressions. The motivation behind this is for the compiler to be able to compute the expression at compile time and write the computed value into the generated object file.
The C standard provides specific rules for what is a constant expression:
An
integer constant expression117)
shall have integer type and shall only have operands
that are integer constants, enumeration constants, character constants,
sizeof
expressions whose results are integer constants,
_Alignof
expressions, 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
or
_Alignof
operator
.
More latitude is permitted for constant expressions in initializers. Such a constant
expression shall be, or evaluate to, one of the following:
an arithmetic constant expression,
a null pointer constant,
an address constant, or
an address constant for a complete object type plus or minus an integer constant
expression.
As you can see non of the cases include an array access expression or a pointer dereference. So "abcd"[2] does not qualify as a constant expression per the standard.
Now the standard also says:
An implementation may accept other forms of constant expressions.
So it would not violate the standard to allow "abcd"[1] as a constant expression, but it's also not guaranteed to be allowed.
So it's up to you whether or not to allow it in your compiler. It will be standard compliant either way (though allowing it is more work as you need another case in your isConstantExpression check and you need to actually be able to evaluate the expression at compile time, so I'd go with disallowing it).
int a = "abcd"[2];
a is a global variable initilize at compile time but the "abcd"[2] is computed at run time.
char b = "abcd"[2];
here b is local variable and it initilize at run time after "abcd"[2] computed.

Details of what constitutes a constant expression in C?

C defines at least 3 levels of "constant expression":
constant expression (unqualified)
arithmetic constant expression
integer constant expression
6.6 paragraph 3 reads:
Constant expressions shall not contain assignment, increment, decrement, function-call,
or comma operators, except when they are contained within a subexpression that is not
evaluated.
So does this mean 1,2 is not a constant expression?
Paragraph 8 reads:
An arithmetic constant expression shall have arithmetic type and shall only have
operands that are integer constants, floating constants, enumeration constants, character
constants, and sizeof expressions. Cast operators in an arithmetic constant expression
shall only convert arithmetic types to arithmetic types, except as part of an operand to a
sizeof operator whose result is an integer constant.
What are the operands in (union { uint32_t i; float f; }){ 1 }.f? If 1 is the operand, then this is presumably an arithmetic constant expression, but if { 1 } is the operand, then it's clearly not.
Edit: Another interesting observation: 7.17 paragraph 3 requires the result of offsetof to be an integer constant expression of type size_t, but the standard implementations of offsetof, as far as I can tell, are not required to be integer constant expressions by the standard. This is of course okay since an implementation is allowed (under 6.6 paragraph 10) to accept other forms of constant expressions, or implement the offsetof macro as __builtin_offsetof rather than via pointer subtraction. The essence of this observation, though, is that if you want to use offsetof in a context where an integer constant expression is required, you really need to use the macro provided by the implementation and not roll your own.
Based on your reading, 1,2 isn't a constant expression. I don't know why it isn't, just that I agree with you that it isn't (despite the fact that it probably should be).
6.5.2 specifies compound literals as a postfix operator. So in
(union { uint32_t i; float f; }){ 1 }.f
The operands are (union { uint32_t i; float f; }){ 1 } and f to the . operator. It is not an arithmetic constant expression, since the first argument is a union type, but it is a constant expression.
UPDATE: I was basing this on a different interpretation of the standard.
My previous reasoning was that (union { uint32_t i; float f; }){ 1 }.f met the criteria for a constant expression, and was therefore a constant expression. I still think it meets the criteria for a constant expression (6.6 paragraph 3) but that it is not any of the standard types of constant expressions (integer, arithmetic, or address) and is therefore only subject to being a constant expression by 6.6 paragraph 10, which allows implementation-defined constant expressions.
I'd also been meaning to get to your edit. I was going to argue that the "hack" implementation of offsetof was a constant expression, but I think it's the same as above: it meets the criteria for a constant expression (and possibly an address constant) but is not an integer constant expression, and is therefore invalid outside of 6.6 paragraph 10.
If 1,2 would be a constant expression, this would allow code like this to compile:
{ // code // How the compiler interprets:
int a[10, 10]; // int a[10];
a[5, 8] = 42; // a[8] = 42;
}
I don't know whether it is the real reason, but I can imagine that emitting an error for this (common?) mistake was considered more important than turning 1,2 into a constant expression.
UPDATE: As R. points out in a comment, the code about is not longer a compiler error since the introduction of VLAs.

Resources