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?
Related
I'm writing some library code that exposes a const pointer to users but during certain operations I need to change where this pointer points (behind the scenes switcheroo tricks). One idea I had to solve this problem without encountering UB or strict-aliasing violations was to use a union with a const member:
// the pointed-to objects (in production code, these are actually malloc'd blocks of mem)
int x = 0, y = 7;
typedef union { int * const cp; int * p; } onion;
onion o = { .cp = &x };
printf("%d\n", *o.cp); // <---------------------- prints: 0
o.p = &y;
printf("%d\n", *o.cp); // <---------------------- prints: 7
But I don't know if this is well-defined or not... anybody know if it is (or isn't) and why?
EDIT: I think I muddied the waters by mentioning I was building a library as lots of people have asked for clarifying details about that rather than answering the much simpler question I intended.
Below, I've simplified the code by changing the type from int* to just int and now my question is simply: is the following well-defined?
typedef union { int const cp; int p; } onion;
onion o = { .cp = 0 };
printf("%d\n", o.cp); // <---------------------- prints: 0
o.p = 7;
printf("%d\n", o.cp); // <---------------------- prints: 7
I think this is undefined as per C11 6.7.3 (equivalent paragraph is in all versions of the standard):
If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.
o.cp is undoubtedly an object defined with a const-qualified type.
The modification of o.p does seem to me to count as an attempt to modify o.cp , since that is exactly why we are doing it!
Every programming book I've had told me the following.
static const int x = 7;
int *px = (int *)&x;
is not defined, but
static int x = 7;
const int *px1 = &x;
int *px2 = (int *)px1;
is defined. That is, you can always cast away the const-ness if the originating pointer (here the &x) wasn't const.
Here I'm leaning on the lack of a contrary opinion from any quality source and not bothering to look up the standard (for which I'm not going to pay).
However you're trying to export something const that isn't const. That is actually valid. The language allows for
extern const * int p;
to be writable behind the secnes. The way to switch it out to the file with the definition doesn't see it const is to define it as int *p; and carefully not include the declaration in the file containing the defintion. This allows you to cast away the const with impunity. Writing to it would look like:
int x;
*((int **)&p) = &x;
Old compilers used to reject extern const volatile machine_register; but modern compilers are fine.
If the interface is a const-declared pointer such as int *const (like you've indicated in your comment), then there's nothing you can do to change that that will not trigger UB.
If you're storing an int * somewhere (e.g., as a static int *ip;) and are exposing its address via a an int *const* pointer (e.g., int *const* ipcp = &ip;, then you can simply recast to back to (int**) (the original type of &ip from the example I gave) and use that to access the int* pointer.
The Standard uses the term "object" to refer to a number of concepts, including:
an exclusive association of a region of storage of static, automatic, or thread duration to a "stand-alone" named identifier, which will hold its value throughout its lifetime unless modified using an lvalue or pointer derived from it.
any region of storage identified by an lvalue.
Within block scope, a declaration struct s1 { int x,y; } v1; will cause the creation of an object called v1 which satisfying the first definition above. Within the lifetime of v1, no other named object which satisfies that definition will be observably associated with the same storage. An lvalue expression like v1.x would identify an object meeting the second definition, but not the first, since it would identify storage that is associated not just with the lvalue expression v1.x, but also with the named stand-alone object v1.
I don't think the authors of the Standard fully considered, or reached any sort of meaningful consensus on, the question of which meaning of "object" is described by the rule:
If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.
It would certainly make sense that if an object of the first kind is defined with a const qualifier, the behavior of code that tries to modify it would be outside the Standard's jurisdiction. If one interprets the rule as applying more broadly to other kinds of objects as well, then actions that modify such objects within their lifetime would also fall outside the Standard's jurisdiction, but the Standard really doesn't meaningfully describe the lifetime of objects of the second type as being anything other than the lifetime of the underlying storage.
Interpreting the quoted text as applying only to objects of the first kind would yield clear and useful semantics; trying to apply it to other kinds of objects would yield semantics that are murkier. Perhaps such semantics could be useful for some purposes, but I don't see any advantage versus treating the text as applying to objects of the first type.
Does test_func the following snippet trigger undefined behavior under the strict aliasing rules when the two arguments partially overlap?
That is the second argument is a member of the first:
#include <stdio.h>
typedef struct
{
//... Other fields
int x;
//... Other fields
} A;
int test_func(A *a, int *x)
{
a->x = 0;
*x = 1;
return a->x;
}
int main()
{
A a = {0};
printf("%d\n", test_func(&a, &a.x));
return 0;
}
Is the compiler allowed to think test_func will just return 0, based on the assumption that A* and int* will not alias? so the *x cannot overwrite the member?
Strict aliasing refers to when a pointer is converted to another pointer type, after which the contents are accessed. Strict aliasing means that the involved pointed-at types must be compatible. That does not apply here.
There is however the term pointer aliasing, meaning that two pointers can refer to the same memory. The compiler is not allowed to assume that this is the case here. If it wants to do optimizations like those you describe, it would perhaps have to add machine code that compares the pointers with each other, to determine if they are the same or not. Which in itself would make the function slightly slower.
To help the compiler optimize such code, you can declare the pointers as restrict, which tells the compiler that the programmer guarantees that the pointers are not pointing at the same memory.
Your function compiled with gcc -O3 results in this machine code:
0x00402D09 mov $0x1,%edx
Which basically means that the whole function was replaced (inlined) with "set a.x to 1".
But if I rewrite your function as
int test_func(A* restrict a, int* restrict x)
{
a->x = 0;
*x = 1;
return a->x;
}
and compile with gcc -O3, it does return 0. Because I have now told the compiler that a->X and x do not point at the same memory, so it can assume that *x = 1; does not affect the result and skip the line *x = 1; or sequence it before the line a->x = 0;.
The optimized machine code of the restrict version actually skips the whole function call, since it knows that the value is already 0 as per your initialization.
This is of course a bug, but the programmer is to blame for it, for careless use of restrict.
This is not a violation of strict aliasing. The strict aliasing rule says (simplified) that you can access the value of an object only using an lvalue expression of a compatible type. In this case, the object you're accessing is the member x of main's a variable. This member has type int. And the expression you use to access it (*x) also has type int. So there's no problem.
You may be confusing strict aliasing with restrict. If you had used the restrict keyword in the declaration of one of the pointer parameters, the code would be invalid because restrict prevents you from using different pointers to access the same object - but this is a different issue than strict aliasing.
I'm developing a hardware abstraction library for an embedded product using GCC C. Within the library there is a variable that should be read-only to the application that links the library, but can be modified from within the compilation unit that defines it.
Is there a standard, acceptable way to declare the integer (in the library header file) that will allow the application to read the value in the variable, but tell the compiler to generate an error if any attempt is made to generate code that writes back to it?
For example, if I were to declare a function as:
extern void foo(int const bar);
... then the caller is permitted to pass a local variable:
int bar = 0;
foo(bar);
... but if the function declaration attempts to write to bar:
void foo(int const bar)
{
bar = 99;
}
... then the compiler duely reports an error: assignment of read-only location 'bar'.
The syntax of placing the const before the name does not appear to apply to variables in the same way that it does to function parameters, and the two lines below seem to be effectively equivalent:
extern const int x;
extern int const y;
... because defining y as int y; results in an error: conflicting type qualifiers for 'y', as I would expect to have seen had x been defined as int x;.
I know that I can get around the problem by declaring and defining an accessor function that returns the value (which can only be used as an r-value).
I've read a number of related questions here, but none that I've found provide a definitive answer for C (as opposed to C++ or C#):
C — Accessing a non-const through const declaration
Mixing extern and const
Please can someone point in me the direction of an example of how I might achieve it, or confirm my suspicion that it is not syntactically achievable?
Within the library there is a variable that should be read-only to the application that links the library, but can be modified from within the compilation unit that defines it.
The solution is to use object-oriented design, namely something called private encapsulation.
module.h
int module_get_x (void);
module.c
static int x;
int module_get_x (void)
{
return x;
}
main.c
#include "module.h"
int main(void)
{
int value = module_get_x();
}
If the variable must be read-write inside the module, then this is the only proper way to do this. All other ways are just some flavour of spaghetti programming.
You can use pointer to get past the issue.
module.c
static int readwrite = 0;
int const * const readonly = &readwrite;
module.h
extern int const * const readonly;
If there is no reason to expose address of the variable, you should prefer Lundins getter approach to have proper encapsulation.
I have been working on some code that is similar to the following:
typedef struct
{
unsigned char x;
unsigned short y;
unsigned char[NUM_DEFINED_ELSEWHERE];
} My_Struct;
static My_Struct my_useful_struct; // Variables initialized elsewhere in code.
void myFunction(const My_Struct * p_my_struct)
{
/* Performs various read-only actions utilizing p_my_struct. */
}
void myOtherFunction(void)
{
static My_Struct * p_struct = &my_useful_struct;
myFunction(p_struct);
}
My code compiles without any problems, but when reviewed I was told that unless I typecast p_struct that this could lead to undefined behavior on certain platforms (i.e. 8051). However, I never even received a warning on the compiler. Is it true that not typecasting the pointer when passing it to the function with (const My_Struct *) could lead to undefined behavior?
The reason that I declared the above function with a pointer to const was because I wanted to be able to handle both a pointer to const and a pointer. Is it bad coding practice not to typecast in the above situation?
Thanks for your help!
This is absolutely fine; the compiler performs an implicit conversion from My_Struct * to const My_Struct *. §6.3.2.3 of the C99 spec says:
For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.
Furthermore, even if you declare the function with two inconsistent declarations, such that one file sees it declared like this:
void myFunction(My_Struct * p_my_struct);
even though it's actually defined like this:
void myFunction(const My_Struct * p_my_struct) { ... }
even that is allowed by the spec, even though the compiler doesn't know to perform an implicit conversion, because My_Struct * and const My_Struct * have the same representation (so the conversion is a no-op, anyway).
(Thanks to Christoph and awoodland for their comments clarifying the latter situation. In a previous version of this answer, I wrongly claimed that that would be undefined behavior.)
Edited to add: The reverse — defining a function with a declaration with a pointer-to-non-const parameter, but calling it using a declaration with a pointer-to-const parameter — is also allowed, for the same reason; but attempting to actually modify the data could well result in undefined behavior, depending on where it's from. (A char * can by initialized a string constant, for example, but it's undefined behavior to try to modify the data in that constant.)
Type casting constness onto or off of things is something I consider bad practice - there is generally nothing wrong with passing a non-const pointer into a function expecting a const one.
The exception is if there is some reason that the data can change part way through the execution (e.g. another thread touching the data pointed at) - then you might have a problem, but its not the sort of problem that typecasting will prevent. In this case you need to make your logic thread safe.
Don't forget that the const keyword can't be used by the compiler to guarantee constness either, although it can be used to detect problems where you think data should not change, but the compiler expects that you want to change it... its more of a documentation tool for you than anything else.
First I assume the & in the myFunction(&p_struct) call is a typo and what you really meant is myFunction(p_struct).
static My_Struct * p_struct = &my_useful_struct;
myFunction(p_struct);
When you pass p_struct there is absolutely no reason to cast the p_struct pointer in the function call. This is perfectly valid to pass a pointer to T in a function where the parameter is pointer to const T.
In the C Standard this is ruled by the constraints of the assignment operator (C99, 6.5.16.1p1). In a function call of a function declared with a prototype, the arguments are converted as if by assignment to the type of the corresponding parameters (C99, 6.5.2.2p7).
This works for me and I don't think the const pointer parameter yields undefined behavior, the compiler does an implicit conversion before calling the function:
typedef struct
{
unsigned char x;
unsigned short y;
unsigned char[NUM_DEFINED_ELSEWHERE];
} My_Struct;
static My_Struct my_useful_struct; // Variables initialized elsewhere in code.
void myFunction(const My_Struct * p_my_struct)
{
/* Performs various read-only actions utilizing p_my_struct. */
}
void myOtherFunction(void)
{
static My_Struct * p_struct = &my_useful_struct;
myFunction(p_struct);
}
I tried the following code
#include <stdio.h>
int main(void)
{
typedef static int sint;
sint i = 10;
return 0;
}
and hit the following error:
error: multiple storage classes in declaration specifiers
When I referred the C99 specification, I came to know that typedef is a storage class.
6.7.1 Storage-class specifiers
Syntax
storage-class-specifier:
typedef
extern
static
auto
register
Constraints: At most, one storage-class specifier may be
given in the declaration specifiers in a declaration
Semantics: The typedef specifier is called a ‘‘storage-class specifier’’
for syntactic convenience only;
The only explanation that I could find (based on some internet search and cross referring various sections in C99 specification) was syntactic convenience only to make the grammar simpler.
I'm looking for some justification/explanation on how can a type name have storage class specifier?
Doesn't it make sense to have a code like typedef static int sint;?
or Where am I going wrong?!
Yes, typedef is a storage-class-specifier as you found in the standard. In part it's a grammatical convenience, but it is deliberate that you can either have typedef or one of the more "obvious" storage class specifiers.
A typedef declaration creates an alias for a type.
In a declaration static int x; the type of x is int. static has nothing to do with the type.
(Consider that if you take the address of x, &x has type int*. int *y = &x; would be legal as would static int *z = &x but this latter static affects the storage class of z and is independent of the storage class of x.)
If something like this were allowed the static would have no effect as no object is being declared. The type being aliased is just int.
typedef static int sint;
Perhaps the standard should have called these things storage-class-or-typedef-specifier and said:
Constraints: At most, one storage-classor-typedef-specifier may be given in the declaration specifiers in a declaration
Then they wouldn't have had to added the note about the semantics.
The comment about the semantics is simply saying that typedef doesn't actually control anything about the storage used for the type (so it isn't semantically a 'storage specifier'), but that it is handled syntactically like the other storage-class-specifier, and therefore cannot be used with them.
So a typedef can't determine where a particular instance of type will be stored - that's determined by the actual declaration of the instance (either implicitly or explicitly).
Even if what you're looking for were permitted, it would be bad practice, I'm sure. Consider:
// in someheader.h
typedef static int sint;
// now in foo.c
#include "someheader.h"
int foo(void)
{
sint i = 10; // unless you're intimately knowledgeable about how
// `sint` is typedef'ed, this looks exactly like
// an automatic
// do some stuff that modifies `i`...
return i;
}
sint bar(void) // what does this mean? is `bar()` static?
{
return foo();
}
Note that is you use the preprocessor to get the 'static typedef' effect, that would make bar() a static function. Which might not be the effect you want. Maybe.
You can't do that -- at least not with MinGW's GCC -- either inside or outside of a function.
I'd use the preprocessor instead:
#include <stdio.h>
#define sint static int
int main(void)
{
sint i = 10;
return 0;
}
Achieves the same result.
I'd imagine it's because "static int" isn't a type in the same way "volatile int" is.
typedef is syntactically same as a storage class. It is not a storage class.
typedef is similar to #define in effect, but typedef is interpreted by
Compiler while #define is by the preprocessor.
typedef can do textual substitutions that are beyond the capabilities of
the preprocessor.
Two purposes for using typedef
1. Portability
2. Better documentation