38

I cannot understand why doing this is wrong:

const int n = 5; 
int x[n] = { 1,1,3,4,5 };

even though n is already a const value.

While doing this seems to be right for the GNU compiler:

const int n = 5;
int x[n]; /*without initialization*/

I'm aware of VLA feature of C99 and I think it's related to what's going on but I just need some clarification of what's happening in the background.

haccks
  • 104,019
  • 25
  • 176
  • 264
Yahia Farghaly
  • 857
  • 2
  • 10
  • 20
  • I suspect if you tack `static` on the definition of `n`, it will work; `static const` would be true global compile time constant, not stack stored constant. – ShadowRanger Feb 02 '16 at 19:24
  • 10
    @ShadowRanger: C is not C++. The semantics of `const` is one of the differences. C does not have symbolic constant. E.g. `1` is an _integer constant_ as of the C standard. `const int i` is still a variable! – too honest for this site Feb 02 '16 at 19:31
  • 3
    `const` is just a promise **you** give to the compiler you will not change a variable, neither directly, nor indirectly. For the first most compilers will warn, for the latter (e.g. through pointers) they are very limited in detecting all possible violations. If you violate the contract you invoke undefined behaviour. – too honest for this site Feb 02 '16 at 19:33
  • 2
    @Olaf, C has *some* symbolic constants where you perhaps wouldn't expect them, namely enumerations. – Jens Gustedt Feb 02 '16 at 20:27
  • @JensGustedt: Yeah, I always forget about those. They are some special kind, mostly because you cannot explicitly specify their type (something C11 should also have copied from C++11 making `enum`s finally useful). And the two "boolean" constants `_True` and `_False` which are not user-definable. Hmm, as you wrote "namely" which implies there are others, too: any other user-definable symbolic constants I forgot about? (serious question, I might have overlooked something simple - if you're in the woods, you only see trees) – too honest for this site Feb 02 '16 at 20:54
  • @Olaf, I probably just have put it weirdly, these are the only ones if you don't account for macros. `enum` constants in C are always `int`, it would have been difficult to go the same path as C++11 without breaking things. (And, there is no such thing as `_True` or `_False` in C. There are only the macros `true` and `false` in `` that resolve to `1` and `0`.) – Jens Gustedt Feb 02 '16 at 21:51
  • @JensGustedt: As macros are a textual replacement done before the concept of constants even comes into the game, I'm confident you don't mean them. But glad I did not forgot about something. For the type: Not sure; `enum : uint8_t {` making the type and all constants `uint8_t` as an extension with the normal `enum {` unchanged would not mess up anything IMO, but make them extremely useful, e.g. for embedded programming and safe a lot of these "nasty" `#define`s. – too honest for this site Feb 02 '16 at 21:56
  • @JensGustedt: About `_True` ... Hmm, I could have sworn they existed. But you are right and thinking about it, they don't make much sense either, as in C `_Bool` is actually a chimera, unlike C++. I really wish they became more courageous cutting off legacy stuff like they did with C99 (partially). Without this stuff C would be quite a good language with much less confusion. – too honest for this site Feb 02 '16 at 22:00
  • @Olaf, I don't really see why it would be usefull to have constants for types that are more narrow than `int`. In almost any context these are promoted to `int`, anyhow. What would be more interesting are wider types. – Jens Gustedt Feb 02 '16 at 22:01
  • @JensGustedt: Not always. Think about assignments, including compound assignments. In embedded systems you often have something like `PORTA |= MYBITMASK`. While that still might extend to `int` first, the compiler could do some more type-checking. Similar when it comes to storing such data in a `struct` on a RAM-restricted system. Even if not: why only allow it for wider types instead of generalising it? – too honest for this site Feb 02 '16 at 22:04
  • This question is related to c99. Do not remove tag please. – haccks Feb 03 '16 at 10:35
  • @haccks this question is *not* explicitly related to the standard revision. The tag is completely irrelevant. – Alex Celeste Feb 03 '16 at 11:17
  • [Can a const variable be used to declare the size of an array in C?](http://stackoverflow.com/q/18848537/995714), [Is it better to use #define or const int for constants?](http://arduino.stackexchange.com/q/506/3606) – phuclv Feb 03 '16 at 13:44
  • @Olaf The fact that you can change a `const` variable with pointers doesn't preclude the compiler from taking your word on your `const` promise and assuming at compile time that `n` will always evaluate to 5. After all, it already leaves the undefined behaviour as your own responsibility to deal with. So I'm not sure what the purpose of your assertion is. – Jordan Melo Feb 05 '16 at 22:02
  • @JordanMelo: You comment is not clear either. What is unclear about "If you violate the contract you invoke undefined behaviour." - I did not even talk about optimisations. My point is in C the semantics of `const` are clearly different from what one e.g. expects from other languages like Pascal - or C++. `const` in C is just a qualifier, much like `restrict`. For both the compiler **may** assume a specific behaviour to optimise the code, but does not have to. Feel free to consult the standard. – too honest for this site Feb 06 '16 at 14:38

5 Answers5

36

The key thing to remember is that const and "constant" mean two quite different things.

The const keyword really means "read-only". A constant is a numeric literal, such as 42 or 1.5 (or an enumeration or character constant). A constant expression is a particular kind of expression that can be evaluated at compile time, such as 2 + 2.

So given a declaration:

const int n = 5;

the expression n refers to the value of the object, and it's not treated as a constant expression. A typical compiler will optimize a reference to n, replacing it by the same code it would use for a literal 5, but that's not required -- and the rules for whether an expression is constant are determined by the language, not by the cleverness of the current compiler.

An example of the difference between const (read-only) and constant (evaluated at compile time) is:

const size_t now = time(NULL);

The const keyword means you're not allowed to modify the value of now after its initialization, but the value of time(NULL) clearly cannot be computed until run time.

So this:

const int n = 5;
int x[n];

is no more valid in C than it would be without the const keyword.

The language could (and IMHO probably should) evaluate n as a constant expression; it just isn't defined that way. (C++ does have such a rule; see the C++ standard or a decent reference for the gory details.)

If you want a named constant with the value 5, the most common way is to define a macro:

#define N 5
int x[N];

Another approach is to define an enumeration constant:

enum { n = 5 };
int x[n];

Enumeration constants are constant expressions, and are always of type int (which means this method won't work for types other than int). And it's arguably an abuse of the enum mechanism.

Starting with the 1999 standard, an array can be defined with a non-constant size; this is a VLA, or variable-length array. Such arrays are permitted only at block scope, and may not have initializers (since the compiler is unable to check that the initializer has the correct number of elements).

But given your original code:

const int n = 5; 
int x[n] = { 1,1,3,4,5 };

you can let the compiler infer the length from the initializer:

int x[] = { 1,1,3,4,5 };

And you can then compute the length from the array's size:

const int x_len = sizeof x / sizeof x[0];
Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 2
    "A constant is a numeric literal". Is this strictly true? There must be some other types of constants? – artm Feb 03 '16 at 08:39
  • 1
    OT, but: "C++ does have such a rule [to evaluate `n` as a constant expression]". Well, only sometimes; to make this phrase really accurate, you'd need to substitute `constexpr`. Using just `const` might work in some cases, but it does not guarantee a "constant expression" in C++, depending on context. e.g.: http://stackoverflow.com/questions/18996258/array-initialization-use-const-variable-in-c – underscore_d Feb 03 '16 at 13:46
  • 1
    @artm: You're right, there are 4 kinds of constants in C: integer, floating, enumeration, and character. – Keith Thompson Feb 03 '16 at 17:00
  • @underscore_d: Yes, the C++ rule is a bit more complicated -- but it does apply in this particular case. I've added some weasel words. – Keith Thompson Feb 03 '16 at 17:05
21

Why int x[n] is wrong where n is a const value?

n is not a constant. const only promise that n is a 'read-only' variable that shouldn't be modified during the program execution.
Note that in , unlike , const qualified variables are not constant. Therefore, the array declared is a variable length array.
You can't use initializer list to initialize variable length arrays.

C11-§6.7.9/3:

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.

You can use #define or enum to make n a constant

#define n 5
int x[n] = { 1,1,3,4,5 };   
haccks
  • 104,019
  • 25
  • 176
  • 264
  • but n is const not variable? – Yahia Farghaly Feb 02 '16 at 19:21
  • 5
    @Yahia Farghaly `n` is a variable, it is `const`, but not constant. `const` effectively means read-only in C. – chux - Reinstate Monica Feb 02 '16 at 19:23
  • 1
    @chux: Actually, it is not guaranteed to be read-only. It is just a guarantee by the programmer to the compiler. The compiler will not be able to detect all violations, nor is the run-time environment required to. – too honest for this site Feb 02 '16 at 19:43
  • 1
    @Oalf - True, It was an mis-guided over-oversimplification for the OP. – chux - Reinstate Monica Feb 02 '16 at 19:52
  • 1
    @haccks interesting - `const` is not constant. So is that correct that in C the only way to make a constant is via `#define`? – artm Feb 02 '16 at 20:01
  • 1
    @artm Yes, since with the `#define` directive the preprocessor will modify the code before handing it off to the compiler. The compiler will never actually see your defined constant, it will only see it's value – Taelsin Feb 02 '16 at 20:04
  • @artm: The C standard uses the term "constant" for what is called "literals" in other languages (it is not consistent, as there are very well "string literals"). `const` is just a qualifier like `restrict` or `volatile`. Like with the other qualifiers, the compiler is allowed to make some assumptions about the usage, but not required to. – too honest for this site Feb 02 '16 at 20:08
  • 2
    note: in C++, `const`-qualified variables may or may not be constant expressions, e.g. `const int n = rand() % 5; int x[n];` is correct in C but not C++ ! – M.M Feb 02 '16 at 21:28
  • 2
    @artm; You can use `#define` or `enum`. – haccks Feb 03 '16 at 10:40
  • @haccks -thanks. So using `#define` we can declare any kind of constants (numeric/int/double/etc, char, string). Using `enum` it must be int constants. – artm Feb 03 '16 at 10:53
  • @artm; Yes, exactly. – haccks Feb 03 '16 at 12:49
  • 1
    `const int n = 5; int x[n];` will work in C++, but `int m = 5; const int n = m; int x[n];` will not. Yes, this is confusing :-) – cpplearner Feb 03 '16 at 13:29
  • @cpplearner; o__O. Thanks for your comment. Never knew that before. – haccks Feb 03 '16 at 13:37
7

If you are exhaustively initialising an array, then it is easier, safer and more maintainable to let the compiler infer the array size:

int x[] = { 1,1,3,4,5 };
const int n = sizeof(x) / sizeof(*x) ; 

Then to change the array size you only have to change the number of initialisers, rather than change the size and the initialiser list to match. Especially useful when there are many initialisers.

Clifford
  • 88,407
  • 13
  • 85
  • 165
3

Even though n is a const, you cannot use it to define the size of an array unless you wish to create a VLA. However, you cannot use an initializer list to initialize a VLA.

Use a macro to create a fixed size array.

#define ARRAY_SIZE 5

int x[ARRAY_SIZE] = { 1,1,3,4,5 };
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • 1
    @chux: You cannot have a static VLA. – too honest for this site Feb 02 '16 at 19:44
  • An enumerated constant would do too if you want to keep the constant within a block or function scope: `enum { n = 5 }; int x[n] = { 1,1,3,4,6 };` – Ian Abbott Feb 02 '16 at 20:05
  • 2
    Hmm, this cannot be a serious advice; one does not pollute the global space of symbols, implying potential and very hard to find bugs, just to create a fixed size array. – Sebastian Mach Feb 03 '16 at 10:11
  • 1
    _Ugh._ Not bothering to change the name makes this terrible advice. Everyone, **please** do not name any macro like this, ever. Just imagine the horror that can result from hijacking any common name, especially a single letter, and _especially_ the exceedingly generic `n`. Macros (or at least 99.9% of them) should have `HUGE_SHOUTY_AND_UGLY_NAMES`, _precisely_ to make them difficult and unappealing to (ab)use. – underscore_d Feb 03 '16 at 13:48
  • @underscore_d,use of `n` was for illustration purposes only but point well made. – R Sahu Feb 03 '16 at 16:37
2

Is your code semantically different from myfunc() here:

void myfunc(const int n) { 
    int x[n] = { 1,1,3,4,5 };
    printf("%d\n", x[n-1]);
    *( (int *) &n) = 17;    //  Somewhat less "constant" than hoped...
    return ;
}

int main(){
    myfunc(4);
    myfunc(5);
    myfunc(6);  //  Haven't actually tested this.  Boom?  Maybe just printf(noise)?
    return 0;
}

Is n really all that constant? How much space do you think the compiler should allocated for x[] (since it is the compiler's job to do so)?

As others have pointed out, the cv-qualifier const does not mean "a value that is constant during compilation and for all times after". It means "a value that the local code is not supposed to change (although it can)".

Eric Towers
  • 4,175
  • 1
  • 15
  • 17