4

I am making a pong clone to test a SDK... It is some years that I don't C.

Anyway, I tried to do this in the const setup phase:

const int SCREEN_W = 480;
const int SCREEN_H = 480;
const int PLAYER_H_WIDTH = 50;
const int PLAYER_H_HEIGHT = 12;
const int BUFFER = 14;
const int LEFT_BUFFER = PLAYER_H_WIDTH+BUFFER;
const int RIGHT_BUFFER = SCREEN_W-LEFT_BUFFER;

except the compiler throws a error at LEFT_BUFFER and RIGHT_BUFFER lines, thus making me wonder, why?

If the answer is "because the standard says so" I still want to know why (why the standard says so?)

EDIT because of comments:

haccks noticed that those lines inside a function (like, main(){}) do compile, while on file scope they don't. I also ask, why?

Also the specific GCC (actually MingW) error is: initializer element is not constant

speeder
  • 6,197
  • 5
  • 34
  • 51
  • 3
    When asking questions about a compiler error, it's always helpful to include the text of the error. It wouldn't hurt to mention which compiler you're using, too. – Caleb Jan 14 '14 at 22:44
  • It is MingW, that says that I tried to init LEFT_BUFFER and RIGHT_BUFFER with something that is not a const. I thought this was implicit from the title. – speeder Jan 14 '14 at 22:45
  • 2
    I compiled it with GCC and getting no error. – haccks Jan 14 '14 at 22:47
  • @haccks I compiled it with gcc4.8.1 and get "error: initializer element is not a constant" in C90, C99 and C11 mode. – dyp Jan 14 '14 at 22:48
  • @haccks Try giving the variables global (or file) scope – simonc Jan 14 '14 at 22:48
  • @dyp; The compile this `#include int main(void) { const int SCREEN_W = 480; const int SCREEN_H = 480; const int PLAYER_H_WIDTH = 50; const int PLAYER_H_HEIGHT = 12; const int BUFFER = 14; const int LEFT_BUFFER = PLAYER_H_WIDTH+BUFFER; const int RIGHT_BUFFER = SCREEN_W-LEFT_BUFFER; return 0; } ` The only expected warning is about unused variables. – haccks Jan 14 '14 at 22:49
  • Here on my system I left those on file scope. – speeder Jan 14 '14 at 22:50
  • @haccks As simonc said, use global or static variables. The problem is C99 6.7.8/4 "All the expressions in an initializer for an object that has static storage duration shall be constant expressions or string literals." – dyp Jan 14 '14 at 22:50
  • @dyp; I understand now. Sorry for that. But OP should have mentioned this in the question. – haccks Jan 14 '14 at 22:51
  • @haccks I did not even knew it made a difference :P – speeder Jan 14 '14 at 22:51
  • @speeder; In programming every bit may make a difference :) – haccks Jan 14 '14 at 22:53
  • @speeder Note that the `const` is (seems to be) irrelevant: The static storage duration is what makes it illegal. – dyp Jan 14 '14 at 22:57
  • @speeder Also note that the example is legal in C++. So it's a deliberate restriction in C, or maybe a simplification. – dyp Jan 14 '14 at 22:58
  • possible duplicate of [Shall I prefer constants over defines?](http://stackoverflow.com/questions/2308194/shall-i-prefer-constants-over-defines) – AnT stands with Russia Jan 14 '14 at 23:28
  • Also http://stackoverflow.com/questions/1674032/static-const-vs-define-in-c – AnT stands with Russia Jan 14 '14 at 23:33

4 Answers4

6

An initializer for an object declared with static storage duration (outside any function, or inside a function with the static keyword) must be a constant expression, or must contain only constant expressions.

This declaration:

const int PLAYER_H_WIDTH = 50;

does not make PLAYER_H_WIDTH a constant expression. The keyword const really doesn't mean "constant" (i.e., able to be evaluated at compile time); it means "read-only".

So when you declare:

const int LEFT_BUFFER = PLAYER_H_WIDTH+BUFFER;

you're trying to initialize LEFT_BUFFER with a non-constant expression.

Reference: ISO C standard, 2011 edition, section 6.7.9 paragraph 4:

All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals.

To illustrate the difference between const and constant, this:

const int r = rand();

is a perfectly valid declaration, as long as it's not at file scope. The object (I don't want to call it a "variable") r is const (i.e., read-only), but not constant (i.e., its value can't be determined at compile time).

That declaration cannot appear outside any function because of C's execution model, which doesn't allow user code to be executed before main is entered.

C++ does make such objects constant if the initializer is constant and of integer type; C does not. The rule in C++ requires a bit more work by the compiler; it make the determination of whether an object's name is a constant expression dependent on the the form of its initializer. I'm not sure why C hasn't adopted the C++ rule; probably the committee didn't feel it was worthwhile (remember that any new feature imposes a burden on every compiler implementer).

As a workaround, you can either use the preprocessor:

#define PLAYER_H_WIDTH 50
/* ... */
#define LEFT_BUFFER (PLAYER_H_WIDTH+BUFFER)

or you can use a slightly ugly hack with enum:

enum { PLAYER_H_WIDTH = 50 };
enum { LEFT_BUFFER = PLAYER_H_WIDTH+BUFFER };

The latter works only for constants of type int. The trick is that an enum declaration creates an enumeration type, but the enumerators are still of type int.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • *"(C++ does make such objects constant; C does not.)"* That leads to the question: Why? Is it the principle "Provide only one way to do an operation."? – dyp Jan 14 '14 at 23:01
  • @dyp: I think it's just historical inertia. – Keith Thompson Jan 14 '14 at 23:07
  • "C++ does make such objects constant if the initializer is constant;" - Only for integer types. For other types, even if the initializer is constant, the object is not. –  Jan 14 '14 at 23:26
1

Your question title shows that you've been misled by some confusion. It has nothing to do with a + b or a - b. You will get the same error if you just do

const int LEFT_BUFFER = BUFFER;

without any + or -.

The problem is that none of the names you defined are "constants", meaning that they cannot form constant expressions. For example, SCREEN_W is not a constant.

In C language the term "constant" applies to literal values (like 42) and to enum constants. Variables declared as const are not "constants" in C language. You can call them "const variables" if you want, but you will never be able to use them where true constants are required.

That is just the way it is in C. That is how it has always been in C. If you want to declare a manifest constant in C language, use #define or enum. Don't attempt to use const. const has some pleasant properties (compared to #define), but the fact that const does not produce true constants in C severely limits the usability of const for declaring manifest constants.

Your original example actually demonstrates the issue. Objects with static storage duration require constant initializers in C. Since your PLAYER_H_WIDTH, BUFFER etc. are not constants, you cannot use them as initializers for objects with static storage duration. And, again, the problem has nothing to do with + or -, it is present even in

const int LEFT_BUFFER = BUFFER;

declaration.

If you transfer these lines into local scope, your objects will no longer have static storage duration. They will become local variables. The aforementioned restriction does not apply to local variables, which is why your declarations will compile without any problems. However, if you add the keyword static to the above declarations, the error will reappear even in local scope.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
0

C's constant is not actually a "constant" as we expected. It is an indicator to the compiler that the variable's content could not be changed. The initialize needs to be constant expressions or string literals.

The closest thing to a constant in C is using enum or #define, e.g.

 enum {
    SCREEN_W = 480,
    SCREEN_H = 480,
    PLAYER_H_WIDTH = 50,
    PLAYER_H_HEIGHT = 12,
    BUFFER = 14,
    LEFT_BUFFER = PLAYER_H_WIDTH+BUFFER,
    RIGHT_BUFFER = SCREEN_W-LEFT_BUFFER
 };

I would use enum as far as I could.

qunying
  • 428
  • 3
  • 4
0

use The #define directive preprocessor if you want to avoid this error !

const int SCREEN_W = 480;
const int SCREEN_H = 480;
const int PLAYER_H_WIDTH = 50;
const int PLAYER_H_HEIGHT = 12;
const int BUFFER = 14;
#define  LEFT_BUFFER  PLAYER_H_WIDTH + BUFFER
#define  RIGHT_BUFFER  SCREEN_W - LEFT_BUFFER

The #define directive is a preprocessor directive; the preprocessor replaces those macros by their body before the compiler even sees it. Think of it as an automatic search and replace of your source code.

But a constant defined with the const qualifier is best thought of as an unmodifiable variable. It has all the properties of a variable: it has a type, it has a size, it has linkage, you can take its address.

for this the compiler can't compile the assignment of const variable to another, because the assigned variable is not a constant expression.

Anis_Stack
  • 3,306
  • 4
  • 30
  • 53
  • 2
    `#define const int LEFT_BUFFER`? and no parens? – dyp Jan 14 '14 at 23:14
  • const value + const value = const value but is not const expression that can be assigned to another const variable. is a compilator behaviour – Anis_Stack Jan 14 '14 at 23:26
  • @Anis_Stack: This does not have anything to do with any "compilator behavior". `value + value` is always an rvalue, which cannot be const or non-const at all. And it makes no sense whatsoever to mix `const` and `#define` like that. Just use `#define` in all cases, how it is normally donw in `C. – AnT stands with Russia Jan 14 '14 at 23:31