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.
Related
This question already has an answer here:
Struct Extension in C
(1 answer)
Closed 2 years ago.
Pointer aliasing in C is normally undefined behavior (because of strict aliasing), but C11 standard seems allow aliasing a pointer to struct and a pointer to the first member of the struct
C11 6.7.2.1 (15)...A pointer to a structure object... points to its initial member... and vice versa...
So does the following code contain undefined behavior?
struct Foo {
int x;
int y;
};
// does foe return always 100?
int foe() {
struct Foo foo = { .x = 10, .y = 20 }, *pfoo = &foo;
int *px = (int*)pfoo; *px = 100;
return pfoo->x;
}
This code is correct. All versions of Standard C and C++ allow this , although the wording varies.
There's no strict aliasing issue because you access an object of type int via an lvalue of type int. The strict aliasing rule may apply when the lvalue doing the access has a different type to the object stored at the memory location .
The text you quoted covers that the pointer cast actually points to the int object.
The way the Standard is written, an lvalue of a structure or union type may be used to access an object of member type, but there is no provision that would allow an arbitrary lvalue of struct or union's member type to access an object of the struct or union type. Because it would of course be absurd to say that code couldn't use a struct or union member lvalue (which would of course have that member's type) to access a struct or union, all compilers have supported some common access patterns. Because compilers allow such accesses under different circumstances, however, the Standard treats all support for such accesses as a Quality of Implementation issue rather than trying to specify exactly when such support is required.
The approach most consistent with the Standard's wording, and which would allow the most useful optimizations, while also supporting most code that would need to perform type punning or other techniques, would be to say that for purposes of N1570 6.5p7, a pointer which is visibly derived from a pointer or lvalue of a given type may be used within the context of such derivation to access things that would (for purposes of 6.5p7) be accessible using an lvalue of that type. Under such an approach, given a piece of code like:
struct foo { int index,len; int *dat; };
void test1(struct foo *p)
{
int *pp = &foo->len;
*pp = 4;
}
void test2(struct foo *p, int dat)
{
if (p->index < p->len)
{
p->dat[p->index] = dat;
p->index++;
}
}
should recognize that within test1, an access to *pp may access the struct foo object *p, since pp is visibly formed from p. On the other hand, the compiler would not be required to accommodate within test2 the possibility that an object of type struct foo, nor members thereof such as p->index, might be modified through the pointer p->dat, because nothing within test2 would cause the address of a struct foo or any portion thereof to be stored in p->dat.
Clang and gcc, however, instead opt for a different approach, behaving as though 6.5p7 allows struct members to be accessed via arbitrary pointers of their types, but union members can't be accessed via pointers at all, excluding the pointer arithmetic implied by bracketed array expressions. Given union { uint16_t h[4]; uint32_t w[2];} u; clang and gcc will recognize that an access to u.h[i] might interact with u.w[j], but will not recognize that *(u.h+i) might interact with *(u.w+j) even though the Standard defines the meaning of the former expressions with brackets as being equivalent to the latter forms.
Given that compilers consistently handle all of these constructs usefully when type-based aliasing is disabled. The Standard, however, doesn't impose any requirements even in many common cases, and clang and gcc make no promises about behavior of constructs not mandated by the Standard, even if all versions to date have handled such constructs usefully. Thus, I would not recommend relying upon clang or gcc to usefully process anything that involves accessing storage as different types at different times except when using -fno-strict-aliasing, and their wackiness isn't an issue when using that option, so I'd recommend simply using that option unless or until clang and gcc adopt a better defined abstraction.
With these definitions:
struct My_Header { uintptr_t bits; }
struct Foo_Type { struct My_Header header; int x; }
struct Foo_Type *foo = ...;
struct Bar_Type { struct My_Header header; float x; }
struct Bar_Type *bar = ...;
Is it correct to say that this C code ("case one"):
foo->header.bits = 1020;
...is actually different semantically from this code ("case two"):
struct My_Header *alias = &foo->header;
alias->bits = 1020;
My understanding is that they should be different:
Case One considers the assignment unable to affect the header in a Bar_Type. It only is seen as being able to influence the header in other Foo_Type instances.
Case Two, by forcing access through a generic aliasing pointer, will cause the optimizer to realize all bets are off for any type which might contain a struct My_Header. It would be synchronized with access through any pointer type. (e.g. if you had a Foo_Type which was pointing to what was actually a Bar_Type, it could access through the header and reliably find out which it had--assuming that's something the header bits could tell you.)
This relies on the optimizer not getting "smart" and making case two back into case one.
The code bar->header.bits = 1020; is exactly identical to struct My_Header *alias = &bar->header; alias->bits = 1020;.
The strict aliasing rule is defined in terms of access to objects through lvalues:
6.5p7 An object shall have its stored value accessed
only by an lvalue expression that has one of the following
types:
The only things that matter are the type of the lvalue, and the effective type of the object designated by the lvalue. Not whether you stored some intermediate stages of the lvalue's derivation in a pointer variable.
NOTE: The question was edited since the following text was posted. The following text applies to the original question where the space was allocated by malloc, not the current question as of August 23.
Regarding whether the code is correct or not. Your code is equivalent to Q80 effective_type_9.c in N2013 rev 1571, which is a survey of existing C implementations with an eye to drafting improved strict aliasing rules.
Q80. After writing a structure to a malloc’d region, can its members be accessed via a pointer to a different structure type that has the same leaf member type at the same offset?
The stumbling block is whether the code (*bar).header.bits = 1020; sets the effective type of only the int bits; or of the entire *bar. And accordingly, whether reading (*foo).header.bits reads an int, or does it read the entire *foo?
Reading only an int would not be a strict aliasing violation (it's OK to read int as int); but reading a Bar_Struct as Foo_Struct would be a violation.
The authors of this paper consider the write to set the effective type for the entire *bar, although they don't give their justification for that, and I do not see any text in the C Standard to support that position.
It seems to me there's no definitive answer currently for whether or not your code is correct.
The fact that you have two structures which contain My_Header is a red herring and complicates your thinking without bringing anything new to the table. Your problem can be stated and clarified without any struct (other than My_Header ofcourse).
foo->header.bits = 1020;
The compiler clearly knows which object to modify.
struct My_Header *alias = &foo->header;
alias->bits = 1020;
Again the same is true here: with a very rudimentary analysis the compiler knows exactly which object the alias->bits = 1020; modifies.
The interesting part comes here:
void foo(struct My_Header* p)
{
p->bits = 1020;
}
In this function the pointer p can alias any object (or sub-object) of type My_header. It really doesn't matter if you have N structures who contain My_header members or if you have none. Any object of type My_Header could be potentially modified in this function.
E.g.
// global:
struct My_header* global_p;
void foo(struct My_Header* p)
{
p->bits = 1020;
global_p->bits = 15;
return p->bits;
// the compiler can't just return 1020 here because it doesn't know
// if `p` and `global_p` both alias the same object or not.
}
To convince you that the Foo_Type and Bar_Type are red herrings and don't matter look at this example for which the analysis is identical to the previous case who doesn't involve neither Foo_Type nor Bar_type:
// global:
struct My_header* gloabl_p;
void foo(struct Foo_Type* foo)
{
foo->header.bits = 1020;
global_p->bits = 15;
return foo->header.bits;
// the compiler can't just return 1020 here because it doesn't know
// if `foo.header` and `global_p` both alias the same object or not.
}
The way N1570 p5.6p7 is written, the behavior of code that accesses individual members of structures or unions will only be defined if the accesses are performed using lvalues of character types, or by calling library functions like memcpy. Even if a struct or union has a member of type T, the Standard (deliberately IMHO) refrains from giving blanket permission to access that part of the aggregate's storage using seemingly-unrelated lvalues of type T. Presently, gcc and clang seem to grant blanket permission for accessing structs, but not unions, using lvalues of member type, but N1570 p5.6p7 doesn't require that. It applies the same rules to both kinds of aggregates and their members. Because the Standard doesn't grant blanket permission to access structures using unrelated lvalues of member type, and granting such permission impairs useful optimizations, there's no guarantee gcc and clang will continue this behavior with with unrelated lvalues of member types.
Unfortunately, as can be demonstrated using unions, gcc and clang are very poor at recognizing relationships among lvalues of different types, even when one lvalue is quite visibly derived from the other. Given something like:
struct s1 {short x; short y[3]; long z; };
struct s2 {short x; char y[6]; };
union U { struct s1 v1; struct s2 v2; } unionArr[100];
int i;
Nothing in the Standard would distinguish between the "aliasing" behaviors of the following pairs of functions:
int test1(int i)
{
return unionArr[i].v1.x;
}
int test2a(int j)
{
unionArr[j].v2.x = 1;
}
int test2a(int i)
{
struct s1 *p = &unionArr[i].v1;
return p->x;
}
int test2b(int j)
{
struct s2 *p = &unionArr[j].v2;
p->x = 1;
}
Both of them use an lvalue of type int to access the storage associated with objects of type struct s1, struct s2, union U, and union U[100], even though int is not listed as an allowable type for accessing any of those.
While it may seem absurd that even the first form would invoke UB, that shouldn't be a problem if one recognizes support for access patterns beyond those explicitly listed in the Standard as a Quality of Implementation issue. According to the published rationale, the authors of the Standard thought compiler writers would to try to produce high-quality implementations, and it was thus not necessary to forbid "conforming" implementations from being of such low quality as to be useless. An implementation could be "conforming" without being able to handle test1a() or test2b() in cases where they would access member v2.x of a union U, but only in the sense that an implementation could be "conforming" while being incapable of correctly processing anything other than some particular contrived and useless program.
Unfortunately, although I think the authors of the Standard would likely have expected that quality implementations would be able to handle code like test2a()/test2b() as well as test1a()/test1b(), neither gcc nor clang supports them pattern reliably(*). The stated purpose of the aliasing rules is to avoid forcing compilers to allow for aliasing in cases where there's no evidence of it, and where the possibility of aliasing would be "dubious" [doubtful]. I've seen no evidence that they intended that quality compilers wouldn't recognize that code which takes the address of unionArr[i].v1 and uses it is likely to access the same storage as other code that uses unionArr[i] (which is, of course, visibly associated with unionArr[i].v2). The authors of gcc and clang, however, seem to think it's possible for something to be a quality implementation without having to consider such things.
(*) Given e.g.
int test(int i, int j)
{
if (test2a(i))
test2b(j);
return test2a(i);
}
neither gcc nor clang will recognize that if i==j, test2b(j) would access the same storage as test2a(i), even when though both would access the same element of the same array.
For example, is the following function legal:
struct two_int {
const int a, b;
}
void copy_two(const two_int *src, two_int *dest) {
memcpy(dest, src, sizeof(two_int));
}
It seems like at least some types of modifications of constant-defined values is not allowed, but it is not clear to me if this qualifies.
If the answer is "it is not allowed, in general", I'm also wondering about the special case where dest is newly allocated memory with malloc (and hence hasn't yet been assigned any value), such as:
two_int s = {.a = 1, .b = 2};
two_int *d = malloc(sizeof(two_int));
copy_two(&s, d);
Update: It seems like the latter question seems to answered in the affirmative (it's OK) for the case of a newly malloc'd structure, but the original, more general question still stands, I think.
Use of memcpy for such purposes would have defined behavior only if the actual destination object does not have static or automatic duration.
Given the code:
struct foo { double const x; };
void outsideFunction(struct foo *p);
double test(void)
{
struct foo s1 = {0.12345678};
outsideFunction(&s1);
return 0.12345678;
}
a compiler would be entitled to optimize the function to:
double test(void)
{
static struct foo const s1 = {0.12345678};
outsideFunction(&s1);
return s1.x;
}
On many processors, the only way to load a double with an arbitrary constant is to load its value from object holding that constant, and in this case the compiler would conveniently know an object (s1.x) which must hold the constant 0.12345678. The net effect would be that code which used memcpy to write to s1.x could corrupt other uses of the numeric constant 0.12345678. As the saying goes, "variables won't; constants aren't". Nasty stuff.
The problem would not exist for objects of allocated duration because memcpy requires a compiler to "forget" everything about what had previously been stored in the destination storage, including any "effective type". The declared types of static and automatic objects exist independent of anything being stored into them, and cannot be erased by "memcpy" nor any other means, but allocated-duration objects only have an "effective type" which would get erased.
First note, that almost anything is allowed, in the wider sense of the word: It might simply have undefined behavior. You can take an arbitrary number, reinterpret_cast<> it as an address, and write to that address - perfectly "legal" C++. As for what it does - usually, no guarantees. Here be dragons.
However, as indicated in an answer to this question:
behavior of const_cast in C++
if the structure you're pointing to was not originally defined as const, and simply got const'ified on the way to your code, then const_cast'ing the reference or pointer to it and setting the pointed-to values is required to succeed as you might expect.
I wrote some thing similar to this in my code
const int x=1;
int *ptr;
ptr = &x;
*ptr = 2;
Does this work on all compilers? Why doesn't the GCC compiler notice that we are changing a constant variable?
const actually doesn't mean "constant". Something that's "constant" in C has a value that's determined at compile time; a literal 42 is an example. The const keyword really means read-only. Consider, for example:
const int r = rand();
The value of r is not determined until program execution time, but the const keyword means that you're not permitted to modify r after it's been initialized.
In your code:
const int x=1;
int *ptr;
ptr = &x;
*ptr = 2;
the assignment ptr = &x; is a constraint violation, meaning that a conforming compiler is required to complain about it; you can't legally assign a const int* (pointer to const int) value to a non-const int* object. If the compiler generates an executable (which it needn't do; it could just reject it), then the behavior is not defined by the C standard.
For example, the generated code might actually store the value 2 in x -- but then a later reference to x might yield the value 1, because the compiler knows that x can't have been modified after its initialization. And it knows that because you told it so, by defining x as const. If you lie to the compiler, the consequences can be arbitrarily bad.
Actually, the worst thing that can happen is that the program behaves as you expect it to; that means you have a bug that's very difficult to detect. (But the diagnostic you should have gotten will have been a large clue.)
Online C 2011 draft:
6.7.3 Type qualifiers
...
6 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. If an attempt is
made to refer to an object defined with a volatile-qualified type through use of an lvalue
with non-volatile-qualified type, the behavior is undefined.133)
133) This applies to those objects that behave as if they were defined with qualified types, even if they are
never actually defined as objects in the program (such as an object at a memory-mapped input/output
address).
Emphasis added.
Since the behavior is left undefined, the compiler is not required to issue a diagnostic, nor is it required to halt translation. This would be difficult to catch in the general case; suppose you had a function like
void foo( int *p ) { *p = ...; }
defined in it's own separate translation unit. During translation, the compiler has no way of knowing if p could be pointing to a const-qualified object or not. If your call is something like
const int x;
foo( &x );
you may get a warning like parameter 1 of 'foo' discards qualifiers or something similarly illuminating.
Also note that the const qualifier doesn't necessarily mean that the associated variable will be stored in read-only memory, so it's possible the above code would "work" (update the value in x) in that you'd successfully update x by doing an end-run around the const semantics. But then you might as well just not declare x to be const.
There is a good discussion of this here:Does the evil cast get trumped by the evil compiler?
I would expect gcc to compile this because:
ptr is allowed to point to x, otherwise reading it would be impossible, although as the comment below says it's not exactly brilliant code and the compiler should complain. Warning options will (I guess) affect whether or not it's actually warned about.
when you write to x, the compiler no longer "knows" that it is writing to a const, all this is in the coders hands in C. However, it does really know, so it may well warn you, depending on the warning options you've selected.
whether it works or not, however, will depend on how the code is compiled, the way const is implemented for the compile options selected and the target CPU and architecture. It may work. Or it may crash. Or you may write to a "random" bit of memory and cause (or not) some freaky effect. It's not a good coding strategy, but that wasn't your question :-)
Bad programmer. No Moon Pie!
If you need to modify a const, copy it to a non-const variable and then work with it. It's const for a reason. Trying to "sneak" around a const can cause serious runtime issues. i.e. the optimizer has likely used the value inline, etc.
const int x=1;
int non_const_x = x;
non_const_x = 2;
I wrote some thing similar to this in my code
const int x=1;
int *ptr;
ptr = &x;
*ptr = 2;
Does this work on all compilers? Why doesn't the GCC compiler notice that we are changing a constant variable?
const actually doesn't mean "constant". Something that's "constant" in C has a value that's determined at compile time; a literal 42 is an example. The const keyword really means read-only. Consider, for example:
const int r = rand();
The value of r is not determined until program execution time, but the const keyword means that you're not permitted to modify r after it's been initialized.
In your code:
const int x=1;
int *ptr;
ptr = &x;
*ptr = 2;
the assignment ptr = &x; is a constraint violation, meaning that a conforming compiler is required to complain about it; you can't legally assign a const int* (pointer to const int) value to a non-const int* object. If the compiler generates an executable (which it needn't do; it could just reject it), then the behavior is not defined by the C standard.
For example, the generated code might actually store the value 2 in x -- but then a later reference to x might yield the value 1, because the compiler knows that x can't have been modified after its initialization. And it knows that because you told it so, by defining x as const. If you lie to the compiler, the consequences can be arbitrarily bad.
Actually, the worst thing that can happen is that the program behaves as you expect it to; that means you have a bug that's very difficult to detect. (But the diagnostic you should have gotten will have been a large clue.)
Online C 2011 draft:
6.7.3 Type qualifiers
...
6 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. If an attempt is
made to refer to an object defined with a volatile-qualified type through use of an lvalue
with non-volatile-qualified type, the behavior is undefined.133)
133) This applies to those objects that behave as if they were defined with qualified types, even if they are
never actually defined as objects in the program (such as an object at a memory-mapped input/output
address).
Emphasis added.
Since the behavior is left undefined, the compiler is not required to issue a diagnostic, nor is it required to halt translation. This would be difficult to catch in the general case; suppose you had a function like
void foo( int *p ) { *p = ...; }
defined in it's own separate translation unit. During translation, the compiler has no way of knowing if p could be pointing to a const-qualified object or not. If your call is something like
const int x;
foo( &x );
you may get a warning like parameter 1 of 'foo' discards qualifiers or something similarly illuminating.
Also note that the const qualifier doesn't necessarily mean that the associated variable will be stored in read-only memory, so it's possible the above code would "work" (update the value in x) in that you'd successfully update x by doing an end-run around the const semantics. But then you might as well just not declare x to be const.
There is a good discussion of this here:Does the evil cast get trumped by the evil compiler?
I would expect gcc to compile this because:
ptr is allowed to point to x, otherwise reading it would be impossible, although as the comment below says it's not exactly brilliant code and the compiler should complain. Warning options will (I guess) affect whether or not it's actually warned about.
when you write to x, the compiler no longer "knows" that it is writing to a const, all this is in the coders hands in C. However, it does really know, so it may well warn you, depending on the warning options you've selected.
whether it works or not, however, will depend on how the code is compiled, the way const is implemented for the compile options selected and the target CPU and architecture. It may work. Or it may crash. Or you may write to a "random" bit of memory and cause (or not) some freaky effect. It's not a good coding strategy, but that wasn't your question :-)
Bad programmer. No Moon Pie!
If you need to modify a const, copy it to a non-const variable and then work with it. It's const for a reason. Trying to "sneak" around a const can cause serious runtime issues. i.e. the optimizer has likely used the value inline, etc.
const int x=1;
int non_const_x = x;
non_const_x = 2;