Why is const int x = 5; not a constant expression in C? - c

I thought C had no more surprises for me, but this surprised me.
const int NUM_FOO = 5;
....
int foo[NUM_FOO];
==>error C2057: expected constant expression
My C++ experience has made me internally deprecate #define as much as possible. So this one was a real surprise. VS2019, compiled with /TC. I thought C99 allowed variable size arrays anyway.
Can anybody explain why the rejection occurs, since the compiler for sure knows at compile time the size of the array?
Is it not the case that C99 allows variable size arrays?

In C, this declaration:
const int NUM_FOO = 5;
doesn't make NUM_FOO a constant expression.
The thing to remember (and yes, this is a bit counterintuitive) is that const doesn't mean constant. A constant expression is, roughly, one that can be evaluated at compile time (like 2+2 or 42). The const type qualifier, even though its name is obviously derived from the English word "constant", really means "read-only".
Consider, for example, that these are a perfectly valid declarations:
const int r = rand();
const time_t now = time(NULL);
The const just means that you can't modify the value of r or now after they've been initialized. Those values clearly cannot be determined until execution time.
(C++ has different rules. It does make NUM_FOO a constant expression, and a later version of the language added constexpr for that purpose. C++ is not C.)
As for variable length arrays, yes, C added them in C99 (and made them optional in C11). But as jamesdlin's answer pointed out, VS2019 doesn't support C99 or C11.
(C++ doesn't support VLAs. This: const int NUM_FOO = 5; int foo[NUM_FOO]; is legal in both C99 and C++, but for different reasons.)
If you want to define a named constant of type int, you can use an enum:
enum { NUM_FOO = 5 };
or an old-fashioned macro (which isn't restricted to type int):
#define NUM_FOO 5
jamesdlin's answer and dbush's answer are both correct. I'm just adding a bit more context.

A variable with the const qualifier does not qualify as a constant expression.
Section 6.6p6 of the C11 standard regarding Constant Expressions states
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
Note that const qualified integer objects are not included.
This means that int foo[NUM_FOO]; is a variable length array, defined as follows from section 6.7.6.2p4:
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; 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.
As for the error you're getting, that is because Visual Studio is not fully compliant with C99 and does not support variable length arrays.

const in C does not declare a compile-time constant. You can use an enum constant instead if you want to avoid using #define and want a symbolic name that can appear in a debugger.
C99 does support VLAs. However, VS2019 does not support C99.

On top of the existing answers which are all good, the reason a const-qualified object fundamentally can't in general be a constant expression, in the sense of "one that can be evaluated at compile time" (as mentioned in Keith's answer), is that it can have external linkage. For example, you can have in foo.c
const int NUM_FOO = 5;
and in bar.c:
extern int NUM_FOO;
...
int foo[NUM_FOO];
In this example, the value of NUM_FOO cannot be known when compiling bar.c; it is not known until you choose to link foo.o and bar.o.
C's model of "constant expression" is closely tied to properties that allow translation units (source files) to be translated (compiled) independently to a form that requires no further high-level transformations to link. This is also why you can't use addresses in constant expressions except for address constant expressions which are limited to essentially the address of an object plus a constant.

Related

Why cannot a const struct field be used as an initializer in C? [duplicate]

I thought C had no more surprises for me, but this surprised me.
const int NUM_FOO = 5;
....
int foo[NUM_FOO];
==>error C2057: expected constant expression
My C++ experience has made me internally deprecate #define as much as possible. So this one was a real surprise. VS2019, compiled with /TC. I thought C99 allowed variable size arrays anyway.
Can anybody explain why the rejection occurs, since the compiler for sure knows at compile time the size of the array?
Is it not the case that C99 allows variable size arrays?
In C, this declaration:
const int NUM_FOO = 5;
doesn't make NUM_FOO a constant expression.
The thing to remember (and yes, this is a bit counterintuitive) is that const doesn't mean constant. A constant expression is, roughly, one that can be evaluated at compile time (like 2+2 or 42). The const type qualifier, even though its name is obviously derived from the English word "constant", really means "read-only".
Consider, for example, that these are a perfectly valid declarations:
const int r = rand();
const time_t now = time(NULL);
The const just means that you can't modify the value of r or now after they've been initialized. Those values clearly cannot be determined until execution time.
(C++ has different rules. It does make NUM_FOO a constant expression, and a later version of the language added constexpr for that purpose. C++ is not C.)
As for variable length arrays, yes, C added them in C99 (and made them optional in C11). But as jamesdlin's answer pointed out, VS2019 doesn't support C99 or C11.
(C++ doesn't support VLAs. This: const int NUM_FOO = 5; int foo[NUM_FOO]; is legal in both C99 and C++, but for different reasons.)
If you want to define a named constant of type int, you can use an enum:
enum { NUM_FOO = 5 };
or an old-fashioned macro (which isn't restricted to type int):
#define NUM_FOO 5
jamesdlin's answer and dbush's answer are both correct. I'm just adding a bit more context.
A variable with the const qualifier does not qualify as a constant expression.
Section 6.6p6 of the C11 standard regarding Constant Expressions states
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
Note that const qualified integer objects are not included.
This means that int foo[NUM_FOO]; is a variable length array, defined as follows from section 6.7.6.2p4:
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; 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.
As for the error you're getting, that is because Visual Studio is not fully compliant with C99 and does not support variable length arrays.
const in C does not declare a compile-time constant. You can use an enum constant instead if you want to avoid using #define and want a symbolic name that can appear in a debugger.
C99 does support VLAs. However, VS2019 does not support C99.
On top of the existing answers which are all good, the reason a const-qualified object fundamentally can't in general be a constant expression, in the sense of "one that can be evaluated at compile time" (as mentioned in Keith's answer), is that it can have external linkage. For example, you can have in foo.c
const int NUM_FOO = 5;
and in bar.c:
extern int NUM_FOO;
...
int foo[NUM_FOO];
In this example, the value of NUM_FOO cannot be known when compiling bar.c; it is not known until you choose to link foo.o and bar.o.
C's model of "constant expression" is closely tied to properties that allow translation units (source files) to be translated (compiled) independently to a form that requires no further high-level transformations to link. This is also why you can't use addresses in constant expressions except for address constant expressions which are limited to essentially the address of an object plus a constant.

How can I edit this code so it compiles on ISO C90 and not just ISO C99? [duplicate]

I'm dynamically calculating the size of an array. Something like:
void foo(size_t limit)
{
char buffer[limit * 14 + 1];
}
But just GCC compiler says:
error: ISO C90 forbids variable length array ‘buffer’
searching on SO I found this answer:
C99 §6.7.5.2:
If the size is an expression that is not an integer constant
expression... ...each time it is evaluated it shall have a value
greater than zero.
So, I did the re-declaration of size limit type variable to:
void foo(const size_t limit)
But it continues to give warning for me. Is this a GCC bug?
const-qualifying a variable doesn't make it a compile-time constant (see C99 6.6 §6 for the defintion of an integer constant expression), and before the introduction of variable-length arrays with C99, array sizes needed to be compile-time constants.
It's rather obvious that const-qualify a variable doesn't make it a compile-time constant, in particular in case of function parameters which won't be initialized until the function is called.
I see the following solutions to your problem:
compile your code as C99 via -std=c99 or -std=gnu99
allocate your buffer via malloc()
use alloca() if available, which is the closest you can come to variable-length arrays with C90
choose a maximum buffer size which is always used and fail if the given limit argument overflows
As a side note, even though C99 allows variable-length arrays, it's still illegal to use the value of an integer variable with static storage duration as size for an array with static storage duration, regardless of const-qualification: While there's nothing which prevents this in principle if the integer variable is initialized in the same translation unit, you'd have to special-case variables with visible defintion from those whose definition resides in a different translation unit and would either have to disallow tentative defintions or require multiple compilation passes as the initialization value of a tentatively defined variable isn't known until the whole translation unit has been parsed.
const does not introduce a constant in C but a read-only variable.
#define SIZE 16
char bla[SIZE]; // not a variable length array, SIZE is a constant
but
const int size = 16;
char bla[size]; // C99 variable length array, size is a constant
C90 doesn't allow variable length arrays. However, you can use the c99-gcc compiler to make this work.
You are compiling with c90-gcc but looking at C99 specifications.
No it is not a bug. You can't use a VLA in C90. When you declared
const size_t limit
that is not a constant expression. A constant expression would be something like a literal value 666.
Note that C differs significantly from C++ in this regard. Even a constant like this
const int i = 666;
is not a constant expression in C. This is the primary reason why constant values are typically declared with #define in C.
As written in your question, this is from C99, not C90, you need to compile it against C99 to be able to use variable length arrays.
A const qualified variable is not an integer constant expression in the sense of the standard. This has to be a literal constant, an enumeration constant, sizeof or some expression composed with these.
Switch to C99 if you may. The gcc option is -std=c99 (or gnu99 if you want gnu extension.)

clang/gcc cannot set global variables to an address constant minus another address constant

The program below compiles without errors.
#include <stdio.h>
char addr_a[8];
char addr_b[8];
unsigned long my_addr = (unsigned long)addr_b - 8; // PASS
// unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a; // FAIL (error: initializer element is not constant)
int main() {
printf("%lx\n", my_addr);
return 0;
}
Interestingly, when I set unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a the compiler throws "error: initializer element is not constant."
I know globals can only be initialized with a constant expression. I also know that the types of constant expressions that can be used in an initializer for a global are specified in section 6.6p7 of the C standard:
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.
Note that an address constant minus an integer constant is allowed, but not an address constant minus another address constant.
Question:
Why does the C standard restrict the ways you can initialize global variables? What is stopping the C standard from accepting unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a?
Why would you want this?
Suppose addr_a and addr_b represent the start and end of the .text section respectively. A program may want to map the .text section, which has size (unsigned long)addr_b - (unsigned long)addr_a. The trusted-firmware-a project does this in Boot Loader stage 2 (BL2). See BL_CODE_END - BL_CODE_BASE, which is used in arm_bl2_setup.c.
Objects with static storage duration (i.e. globals, plus locals defined as static) can only be initialized with a constant expression.
The types of constant expression that can be used in an initializer for such an object is specified in section 6.6p7 of the C standard:
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.
Note that an address constant plus an integer constant is allowed, but not an address constant plus another address constant.
Granted this still isn't exactly what you have, as you have address constants casted to integer type. So let's check 6.6p6 which defines an integer constant expression:
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.
This paragraph doesn't allow for casting an address constant to an integer type as part of an integer constant expression, but apparently this seems to be supported as an extension.
What is stopping the C standard from accepting unsigned long my_addr = (unsigned long)addr_a + (unsigned long)addr_b?
The underlying reason is "Because why would anyone want that?" It's not meaningful to add two absolute addresses together; the result isn't the address of anything in particular.
It's thus a sort of chicken-and-egg thing. The language doesn't support it because it's useless, but also because existing linkers and object file formats don't support such a relocation. For instance, for ELF on x86-64, see the psABI Table 4.9 for a list of supported relocations, and note there is no S+S. And the linkers don't support it because it's useless, and because the language doesn't require it to be supported.
I guess originally, the tools probably came before the language (the earliest C compilers would have used linkers designed for assembly programs). So the original tools probably didn't support this, the language saw no need to demand that they do so, and over time, neither one ever saw a need to add it.

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.

ISO C90 forbids variable length array

I'm dynamically calculating the size of an array. Something like:
void foo(size_t limit)
{
char buffer[limit * 14 + 1];
}
But just GCC compiler says:
error: ISO C90 forbids variable length array ‘buffer’
searching on SO I found this answer:
C99 §6.7.5.2:
If the size is an expression that is not an integer constant
expression... ...each time it is evaluated it shall have a value
greater than zero.
So, I did the re-declaration of size limit type variable to:
void foo(const size_t limit)
But it continues to give warning for me. Is this a GCC bug?
const-qualifying a variable doesn't make it a compile-time constant (see C99 6.6 §6 for the defintion of an integer constant expression), and before the introduction of variable-length arrays with C99, array sizes needed to be compile-time constants.
It's rather obvious that const-qualify a variable doesn't make it a compile-time constant, in particular in case of function parameters which won't be initialized until the function is called.
I see the following solutions to your problem:
compile your code as C99 via -std=c99 or -std=gnu99
allocate your buffer via malloc()
use alloca() if available, which is the closest you can come to variable-length arrays with C90
choose a maximum buffer size which is always used and fail if the given limit argument overflows
As a side note, even though C99 allows variable-length arrays, it's still illegal to use the value of an integer variable with static storage duration as size for an array with static storage duration, regardless of const-qualification: While there's nothing which prevents this in principle if the integer variable is initialized in the same translation unit, you'd have to special-case variables with visible defintion from those whose definition resides in a different translation unit and would either have to disallow tentative defintions or require multiple compilation passes as the initialization value of a tentatively defined variable isn't known until the whole translation unit has been parsed.
const does not introduce a constant in C but a read-only variable.
#define SIZE 16
char bla[SIZE]; // not a variable length array, SIZE is a constant
but
const int size = 16;
char bla[size]; // C99 variable length array, size is a constant
C90 doesn't allow variable length arrays. However, you can use the c99-gcc compiler to make this work.
You are compiling with c90-gcc but looking at C99 specifications.
No it is not a bug. You can't use a VLA in C90. When you declared
const size_t limit
that is not a constant expression. A constant expression would be something like a literal value 666.
Note that C differs significantly from C++ in this regard. Even a constant like this
const int i = 666;
is not a constant expression in C. This is the primary reason why constant values are typically declared with #define in C.
As written in your question, this is from C99, not C90, you need to compile it against C99 to be able to use variable length arrays.
A const qualified variable is not an integer constant expression in the sense of the standard. This has to be a literal constant, an enumeration constant, sizeof or some expression composed with these.
Switch to C99 if you may. The gcc option is -std=c99 (or gnu99 if you want gnu extension.)

Resources