What are the implications of using static const instead of #define? - c

gcc complains about this:
#include <stdio.h>
static const int YY = 1024;
extern int main(int argc, char*argv[])
{
static char x[YY];
}
$ gcc -c test1.c
test1.c: In function main':
test1.c:5: error: storage size ofx' isn't constant
test1.c:5: error: size of variable `x' is too large
Remove the “static” from the definition of x and all is well.
I'm not exactly clear what's going on here: surely YY is constant?
I had always assumed that the "static const" approach was preferable to "#define". Is there any way of using "static const" in this situation?

In C, a const variable isn't a "real" compile-time constant... it's really just a normal variable that you're not allowed to modify. Because of this, you can't use a const int variable to specify the size of an array.
Now, gcc has an extension that allows you to specify the size of an array at runtime if the array is created on the stack. This is why, when you leave off the static from the definition of x, the code compiles. However, this would still not be legal in standard C.
The solution: Use a #define.
Edit: Note that this is a point in which C and C++ differ. In C++, a const int is a real compile-time constant and can be used to specify the size of arrays and the like.

You may use 'enum' or 'define' to declare the size:
#define XX 1024
static int const YY = 1024;
enum{ ZZ = 1024 };
extern int main(void){
static char x[XX]; // no error
*(int*)&XX = 123; // error: lvalue required as unary ‘&’ operand
static char y[YY]; // error: storage size of ‘y’ isn’t constant
*(int*)&YY = 123; // no error, the value of a const may change
static char z[ZZ]; // no error
*(int*)&ZZ = 123; // error: lvalue required as unary ‘&’ operand
}

Because you declared x as 'static' that makes it a global variable. Its just known only to the main() function in which it is declared. By declaring YY outside of any function, you have made it global. 'static' also makes it a global, but known only to this file.
If you declared YY as just 'const int YY = 1024', the compiler might treat it like a #define, but with a type. That depends on the compiler.
At this point 2 things might be wrong.
1:
All globals are initialized at runtime, before main() is called.
Since both x and YY are globals, they are both initialized then.
So, the runtime initialization of global x will have to allocate space according to the value in YY. If the compiler is not treating YY like #define with a type, it has to make a compile-time judgement about runtime values. It may be assuming the largest possible value for an int, which really would be too big. (Or possibly negative since you left it signed.)
It may be interesting to see what happens if you only change YY to a short, preferably an unsigned short. Then its max would be 64K.
2:
The size of global space may be limited on your system. You didn't specify the target platform and OS, but some have only so much.
Since you declared x as size YY, you have set it to take YY chars from global space. Every char in it would essentially be a global. If the global space on your system is limited, then 1024 chars of it may be too much.
If you declared x as a pointer to char, then it would take sizeof(char*) bytes. (4 bytes is the size of a pointer on most systems.) With this, you would need to set the pointer to the address of properly malloc'd space.
By declaring x without 'static', it becomes a local variable and is only initialized once the owning function is executed. And its space is taken from the stack, not global space. (This can still be a problem for systems or threads with very limited stack.) YY's value has long since been set by this point, so there is no problem.
Also:
I don't recall if there is any guarantee that globals are initialized in any order. If not, then x could be initialized before YY. If that happened then YY would just contain the random contents of RAM.

To follow on from Martin B's answer, you could do this:
#include <stdio.h>
#define XSIZE 1024
static const int YY = XSIZE;
int main(int argc, char*argv[])
{
static char x[XSIZE];
}

/* SHOULDN'T THIS WORK IN C++? */
// .h
class C {
public:
static const int kSIZE;
int a[kSIZE]; // error: array bound is not an integer constant
};
// .cc
const int C::kSIZE = 1;
/* WORKS FINE */
// .h
class C {
public:
enum eCONST { kSIZE = 1 };
int a[kSIZE];
};

Related

Declaring as const, but defining as non-const, C

Is it valid to declare in my headers a const variable, but define it as a non-const variable for internal use?
The only logical way of declaring variables in the header file is to declare them as extern
As #include only inserts the text of the header file into the source code file (https://godbolt.org/z/nor8nz) you can simply test you idea in the single source file:
extern const int x;
int x;
https://godbolt.org/z/PWEzGM
You will get the error:
1
ARM gcc 8.2
- 347ms
<source>:4:5: error: conflicting type qualifiers for 'x'
int x;
^
<source>:2:18: note: previous declaration of 'x' was here
extern const int x;
^
Compiler returned: 1
No, this causes undefined behavior.
C17 6.6.7 (2):
All declarations that refer to the same object or function shall have compatible type; otherwise, the
behavior is undefined.
6.7.3 (11):
For two qualified types to be compatible, both shall have the identically qualified version of a
compatible type; the order of type qualifiers within a list of specifiers or qualifiers does not affect the
specified type.
Since const is a qualifier, types such as int and const int are not compatible, and therefore it is undefined behavior to have both an int and a const int declaration of the same object. (And the definition of the object is itself a declaration, so it counts.)
One of many possible undesired consequences, in practice, could be that in source files (translation units) that have a const declaration, the compiler may assume that the variable is actually constant, and may fail to notice if it is modified. For example, code like the following:
#include <stdio.h>
extern const int x;
void bar(void);
void foo(void) {
printf("%d\n", x);
bar();
printf("%d\n", x);
}
may cache the value of x in a callee-saved register across the call to bar(), and so will always print the same value twice. See on godbolt. This would happen even if another file contains
int x = 5;
void bar(void) {
x = 6;
}
I hope for better answers that can address the direct question about the validity.
But this is almost certainly a bad approach. My suggestion is to use a function that returns the value instead. Another option would be to use a pointer to const. Something like:
int x;
int *const ptr = &x;
Using globals the way you describe is dangerous territory. If I ever would like to do something like that, I'd probably just accept that I have to be really careful.
One might be tempted to use a pointer like this:
const int x;
int *ptr = &x; // Bad, bad idea
But do NOT try to do that, as it invokes undefined behavior. Source: Can we change the value of an object defined with const through pointers?

How can I reliably create a symbol that marks the end of an object?

I am working on a project where I need to create the address range of many, many global variables in C (C++ not possible), with clang. For symbols of complete types, this is easy in a standard-compliant way:
typedef struct range {
void* begin;
void* end;
} range;
extern int foo;
range foo_range = { &(&foo)[0], &(&foo)[1] };
But as I said, it works because the C compiler statically knows the size of foo, so it's able to resolve &(&foo)[1] as foo+4 bytes (assuming that sizeof(int) is 4, of course). This won't work for symbols of an incomplete type:
struct incomplete;
struct trailing_array {
int count;
int elements[];
};
extern int foo[];
extern struct incomplete bar;
extern struct trailing_array baz;
range foo_range = { &(&foo)[0], &(&foo)[1] };
// error: foo has incomplete type
range bar_range = { &(&bar)[0], &(&bar)[1] };
// error: bar has incomplete type
range bar_range = { &(&baz)[0], &(&baz)[1] };
// this one compiles, but the range excludes the elements array
However, it's not a problem for me to describe these symbols some more. For instance, I can easily add metadata:
// foo.h
extern int foo[];
extern size_t foo_size;
// foo.c
int foo[] = {1,2,3};
size_t foo_size = sizeof(foo);
Except that this won't help my problem for references outside of foo.c, because foo_size is not a compile-time constant, and therefore this wouldn't work:
range foo_range = { &foo, (void*)&foo + foo_size };
// error: foo_size not a compile-time constant
What would work, however, is getting the address of a symbol that ends right where my object ends. For instance, if I define foo with this assembly code:
_foo:
.long 1
.long 2
.long 3
_foo_end:
Then, in my C code, I can have:
extern int foo[];
extern int foo_end;
range foo_range = { &foo, &foo_end };
and that effectively solves my problem.
However, while I have the flexibility to add symbols, I don't have the flexibility to rewrite every global declaration as a file-level assembly statement. So, my question is: what is the closest that I can get to that using clang?
I know that I can use sections (since the linker makes start and end symbols for sections), but one section per global variable would be way overkill.
I know that I can't just take the address of a variable immediately after the global whose range I want to get, because the compiler has been known to reorder globals in some cases.
I'm specifically using Apple's linker, but if you have a solution that works for GNU ld/gold or lld, I'll still take it and see if I can get it to work here too.
Hm, there's no real way to do it if you're defining it in another translation unit. If you want, you can include a types.h file with contents
struct incomplete {
char data[SIZE];
}
Where SIZE is whatever integer you please, and do the same for each global variable. This will intersect future definitions however. Really, you'd have to go with
#define INCOMPLETE_SIZE 5
And then use that for range = { &bar, (void*)&bar + INCOMPLETE_SIZE }.
"Incomplete type" is just some standards terminology for properly describing how to parse
struct A {
A* ptr;
}
As far as I know, they don't really get used otherwise.
I also don't recommend &(&foo)[0], &(&foo)[1] as a way to get a range of pointers, it's very esoteric / hard to read. Much preferred is &foo, &foo + 1. You can see how one can turn this into a solution for bar by doing &bar, (void*)&bar + SIZE, where SIZE is some constant you have to specify somewhere in the code (Either by declaring it and using sizeof / &foo+1 solution, or defining the SIZE with a #define)

initialize the array with pointers to ints

Hey I'm working on a problem and here is what I have to do:-
Write a function called initarray that takes an array of pointers to int and an int representing the size of the array, as arguments. The function should initialize the array with pointers to ints (use malloc) that have a value corresponding to the array indices at which a pointer to them are stored (the pointer stored at array index 2 should point to an integer with a value of 2).
So far I've written this, but It's giving me an error "[Error] variable-sized object may not be initialized"
Can you tell me what I'm doing wrong here?
#include<stdio.h>
void initArray(int **a, int sz){
int i;
for (i = 0; i < sz; i++) {
a[i] = calloc (1, sizeof **a);
*a[i] = i;
}
}
int main(){
const int Var = 10;
int *array[Var] = {NULL};
initArray(array,3);
}
For historical reasons, the value of a const variable is never considered a constant expression in C.
So if you use it as an array dimension, then the array is a variable-length array, and variable-length arrays are not allowed to have initializers.
One solution not mentioned yet is to use enum. Enumerators are in fact constant expressions, and they don't suffer from the same "bigger hammer" issue as preprocessor macros:
int main()
{
enum { Var = 10 };
int *array[Var] = {NULL};
initArray(array,3);
}
C has no symbolic constants with user-defined type. You encountered one of the differences to C++.
The const qualifier just is a guarantee you give to the compiler you will not change the variable(!) Var.
Arrays with initialiser and global arrays require a constant expressing which can be evaluated at compile-time. As Var is semantically still a variable, you cannot use it.
The C-way to emulate symbolic constants are macros:
#define ARRAY_SIZE 10
...
// in your function:
int *array[ARRAY_SIZE] = ...
Macros are handled by the preprocessor and are a textual replacement before the actual compiler sees the code.
Note I changed the name to a more self-explanatory one. The macro should also be at the file-level, typically near the beginning to allow easier modifications. Using the integer constant 10 directly in the code is a bad idea. Such magic numbers are often cause of errors when a modification is required.
The error would suggest that you can't use an initializer (the = {NULL} in your main function) on a variable-sized object. While it looks like it isn't variable (because of the const on Var, and because 10 is a constant) it sees it as variable because you're accessing it through a variable. If you use:
int *array[10] = {NULL}
I think your snippet will work fine.

Initializing global variable works with integer literal but not with const type variable [duplicate]

This question already has answers here:
Error "initializer element is not constant" when trying to initialize variable with const
(8 answers)
Closed 7 years ago.
I'm working through some openGL tutorials and since they all have C++ syntax I need to convert them to C syntax and I have some problems with global variables.
So I have my extern declarations in the shared header LUtil.h
#ifndef LUTIL_H
#define LUTIL_H
#include "LOpenGL.h"
#include <stdio.h>
#include <stdbool.h>
//Color modes
extern const int COLOR_MODE_CYAN;
extern const int COLOR_MODE_MULTI;
//Screen constants
extern const int SCREEN_WIDTH;
extern const int SCREEN_HEIGHT;
extern const int SCREEN_FPS;
extern int gColorMode;
extern GLfloat gProjectionScale;
...
And I have my LUtil.c file in which the declaration happens
#include "LUtil.h"
//The current color rendering mode
const int COLOR_MODE_CYAN = 0;
const int COLOR_MODE_MULTI = 1;
//constants
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_FPS = 60;
//The projection scale
int gColorMode = 0;
GLfloat gProjectionScale = 1.f;
...
Now if I compile like this it works. But if I initialize the gColorMode constant like this in LUtil.c
int gColorMode = COLOR_MODE_CYAN;
I get a compiler error saying that my initializer is not constant despite having declared COLOR_MODE_CYAN a const and initializing with it.
Why is this?
In C, a variable declared const is not a constant, it's called const-qualified variable. It is not considered a compile time constant expression.
You need to either use an integer constant or a #define to get your work done.
FWIW, a variable with const is a real constant (integral constant expression) in case of C++.
const in C doesn't actually create a "constant". You still end up with a variable (reserved memory) but the compiler just forbids writes to that variable.
Even though it is marked const, you could cast it back to non-const and modify it (please don't!) Because of this (and possibly other reasons), it requires emitting a memory read to access the value of your const int constants, which is not allowed when initializing another variable (it must be a compile-time constant.)
Instead, use a #define in your header file.
const int COLOR_MODE_CYAN
is still a variable even if it is constant(which just means that compiler will throw a compile time error if you try to modify it using code). Which means it will have a memory location and a value, when the program will be loaded into memory. Its value can be calculated by the = operator. And all the operators should be placed inside a function definition because they can be resolved only at run time.
And since the variable which you are assigning the value to is a global variable. Its initial value must be declared at compile time so that the compiler can put it into an appropriate segment(.bss or .data). But the assigned value is a variable, and in order to find out its value = operator has to be executed. Which can not be done at compile time. So that compiler is throwing an error that GIVE ME A CONSTANT VALUE THAT I CAN ASSIGN TO THIS GLOBAL VARIABLE LIKE 3, 4, 5.

const pointer pointing to a const pointer

I'm working with some memory pointers. I don't want to use hash defines, please leave that discussion aside. I would just like to know why this does not compile:
#include <stdio.h>
static const unsigned long *const pMemAddrA = (unsigned long *) 0x00000200ul;
static const unsigned long *const pMemAddrB = pMemAddrA;
int main (void)
{
printf("%x", (unsigned int) pMemAddrB);
return 0;
}
Compiler output gcc:
||=== TestConst, Debug ===|
...main.c|4|error: initializer element is not constant|
||=== Build finished: 1 errors, 0 warnings ===|
EDIT:
After reading the answers, I'm happy to know how to go about this problem.
However I do not understand why it is a problem. From what I know static memory gets allocated at program start. I know there is issue if variables "live" in different files and the order in which the variables are allocated cannot be guaranteed by the compiler. However, if both variables "live" in the same file - just as both variables living in the same function - I would think the compiler can assure that memory gets allocated in the order of variables being declared in the file, and therefore I don't understand why declaring and initializing a const pointer to another const pointer is an issue. I'd be happy if someone could enlighten me.
Your pointers have file scope, so the initialisers must be constant expressions. pMemAddrA isn't a constant expression, therefore can't be used to initialise a variable with static storage.
It can be used to initialise a variable in block scope, so if you move your declarations inside main (and make at least the second non-static), it will compile:
#include <stdio.h>
int main (void)
{
const unsigned long *const pMemAddrA = (unsigned long *) 0x00000200ul;
const unsigned long *const pMemAddrB = pMemAddrA;
printf("%x", (unsigned int) pMemAddrB);
return 0;
}
If the two pointers must be declared at file scope, there is no way to prevent either repeating the initialising expression,
static const unsigned long *const pMemAddrA = (unsigned long *) 0x00000200ul;
static const unsigned long *const pMemAddrB = (unsigned long *) 0x00000200ul;
or #defineing it.
You don't describe what "does not work", but I guess you mean that the line
static const unsigned long *const pMemAddrB = pMemAddrA;
produces the error
error: initializer element is not constant
.
The solution is that indeed this initializer is not considered as constant. Instead, a memory area for pMemAddrA is set aside and the value 0x00000200ul is written in there. From there on, it is a value which sits somewhere in memory, and not a constant expression.
Depending on what you want to do with that, you could add another pointer indirection such as
static const unsigned long *const * const pMemAddrB = &pMemAddrA;
and access it with *pMemAddrB instead of pMemAddrB.

Resources