I don't understand why the following code works
typedef struct {
double x;
double y;
double z;
} abc;
static void test(abc const* b)
{
(void)memset((void *)&(b->x), 0, sizeof(double));
}
int main(int argc, char *argv[])
{
abc blah = {11, 12, 23};
printf("\n%f - %f - %f\n", blah.x, blah.y, blah.z);
test(&blah);
printf("\n%f - %f - %f\n", blah.x, blah.y, blah.z);
while (1);
}
In the function test, I have set b as a pointer pointing to a const value. And yet I'm able to change the value of x.
You are using a cast to hack away the const-context of the pointer passed into test, so your operation is permitted at compilation.
blah.x is in fact mutable, so there's nothing undefined about this. It's just really poor style.
Your program's behaviour would be undefined if the object itself were const:
[C99: 6.7.3/5]: 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.
However, creating a const-qualified handle to that object doesn't make the object const; it only means you are prevented (at compile-time) from mutating it through that handle, unless you hack away the constness as you have done here.
You're explicitly taking the address of a struct member that you got via a const pointer; there's nothing stopping you from modifying data at any address in your process' memory here; the mere fact that you consider the struct to be constant doesn't mean you can't mess with the memory containing that struct.
Your (void*) cast is casting away the const-ness of b; memset is able to modify the memory at that location.
That is well-defined since b ultimately points to memory that is not const.
In your code, you made *b as constant. That does not stop you from going ahead, take an address of a member variable of that structure, casting it to void * (and taking away the constness) and modify the value pointed by that pointer.
This should be undefined behavior, and it happend to work.
N1256 6.7.3 Type qualifiers
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.
const is just syntactic sugar for the compiler. There's nothing different about the memory slot where your constant is stored v.s. the memory slot where a normal variable is stored. It's all just memory, and it's all read/write memory.
Marking something as const just causes the compiler to forbid EXPLICIT attempts at modifying the value of the constant. e.g. (pseudo-code)
const x = 42;
x = 43; // compiler error
But doing
const int x = 42;
*int y = &x;
*y = 43; // works
because the compiler is NOT going to check every single pointer/memory access to see if it's trying to scribble on a "constant" memory block. Performance would be insanely bad, because every single memory access would require a bunch of extra code/scanning to check for "forbidden" operations.
Related
I have the following piece of code:
void TestFunc(const void * const Var1, const float Var2)
{
*(float*)Var1 = Var2;
}
It looks like I am changing the value of the const object the const pointer points to (thanks sharptooth), which should not be allowed. Fact is, none of the compilers I tried issued a warning. How is this possible?
As others mentioned, the cast removes the 'constness' of the destination as far as the expression is concerned. When you use a cast the compiler treats the expression according to the cast - as long as the cast itself it valid (and C-style casts are pretty much the big hammer). This is why you don't get an error or warning. You're essentially telling the compiler, "be quiet, I know what I'm doing, this is how you should treat things". In fact, casts are probably the #1 way for programmers to get the compiler to stop issuing warnings.
Your assignment expression may or may not be undefined behavior. It is permitted to cast away constness if the object actually pointed to is not const.
However, if the object pointed to is const, then you have undefined behavior.
void TestFunc(const void * const Var1, const float Var2)
{
*(float*)Var1 = Var2;
}
int
main(void)
{
float x = 1.0;
const float y = 2.0;
TestFunc( &x, -1.0); // well defined (if not particularly great style)
TestFunc( &y, -2.0); // undefined behavior
return 0;
}
You're treading dangerous waters...
In general (I'm sure there are exceptions), casting so that expressions treat objects as they really are is supported, well-defined behavior in C/C++.
This particular behavior is covered in the standards mostly by statements that modifying a const object through a cast (or something) that removes the const qualifier is undefined. The inference is that doing the same for a non-const object is not undefined. An example given in the C++ standard makes this clear.
C90 6.5.3 - Type Qualifiers (C99 6.7.3):
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.
C++ 7.1.5.1 The cv-qualifiers
A pointer or reference to a cv-qualified type need not actually point or refer to a cv-qualified object, but it is treated as if it does; a const-qualified access path cannot be used to modify an object even if the object referenced is a non-const object and can be modified through some other access path. [Note: cv-qualifiers are
supported by the type system so that they cannot be subverted without casting (5.2.11). ]
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const
object during its lifetime (3.8) results in undefined behavior.
...
[Example:
...
int i = 2; //not cv-qualified
const int* cip; //pointer to const int
cip = &i; //OK: cv-qualified access path to unqualified
*cip = 4; //ill-formed: attempt to modify through ptr to const
int* ip;
ip = const_cast<int*>(cip); //cast needed to convert const int*to int*
*ip = 4; //defined: *ip points to i, a non-const object
const int* ciq = new const int (3); //initialized as required
int* iq = const_cast<int*>(ciq); //cast required
*iq = 4; //undefined: modifies a const object
You change the object the pointer points to, not the pointer value.
C-style cast acts like a const_cast and removes the const modifier off the pointer. The compiler now has nothing to moan about.
The cast is legal, but the behaviour is undefined.
So I was reading the book Language C by Kernighan Ritchie and on page 39, Chapter 2: Types, Operators and Expressions
the author writes:
The const declaration can also be used with array arguments, to indicate that the function does not change that array:
int strlen(const char[]);
The result is implementation-defined if an attempt is made to change a const.
I don't understand what it means. Would appreciate if anyone could simplify what he means by that.
"Implementation defined" simply means that it is up to the implementation what should happen. A difference from "undefined behavior" is that when it is "implementation defined", the behavior needs to be documented. Read more about that here: Undefined, unspecified and implementation-defined behavior
But you can change things via a const pointer if you cast it to non-const. This will print 42;
void foo(const int *x)
{
*(int *)x = 42;
}
int main(void)
{
int n = 69;
foo(&n);
printf("%d\n", &n);
}
I wrote a related answer about const that you can read here: https://stackoverflow.com/a/62563330/6699433
Declaring function parameters as const indicates that the function should not change the value of those parameters.
Even though C passes arguments by value, pointed-to values are susceptible to change. By declaring the function parameter as const, if the function attempts to modify the pointed-to value, the compiler will generate an error.
The following function will change the value pointed to by x:
void foo(int *x)
{
*x = 100;
}
In the following function, by marking the parameter as const, the function will not be able to change the value pointed to by x.
void foo(const int *x)
{
*x = 100; // Compiler generates an error
}
In C, even though it looks like you're passing an array when using the square brackets [], you're actually passing a pointer. So void foo(const int *x) would be the same as void foo(const int x[])
Summary
Kernighan and Ritchie are wrong; attempting to modify const objects is undefined, not implementation-defined.
This rules applies only to objects originally defined with const.
const on function parameters is advisory, not enforced. It is possible, and defined by the C standard, for a function to modify an object pointed to with a const parameter if that object was not defined with const.
Details
The quoted passage is wrong. Attempting to modify an object defined with const has undefined behavior, not implementation-defined behavior. And this applies only to objects defined with const, not to objects passed via const-qualified pointers, if those objects were not originally defined with const.
C 2018 6.7.3 7 says:
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.
The same wording appears in C 1990 6.5.3.
“Undefined“ means the C standard does not impose any requirements on the behavior (C 2018 3.4.3). This is different from “implementation-defined,” which means the C implementation must document how a choice among possibilities is made (C 2018 3.4.1).
Note that that rule applies only to objects defined with const. 6.7 5 tells us that, for object identifiers, a definition is a declaration that causes storage to be reserved for the object. If we declare int x; inside a function, that will cause storage to be reserved for x, so it is a definition. However, the statement int strlen(const char[]); merely declares a function and its parameter type. The actual parameter is not declared because there is no name for it. If we consider the actual function definition, such as:
int strlen(const char s[])
{
…
}
then this function definition includes a declaration of the parameter s. And it does define s; storage for the parameter itself will be reserved when the function executes. However, this s is only a pointer to some object that the caller passes the address of. So this is not a definition of that object.
So far, we know the rule in 6.7.3 7 tells us that modifying an object defined with const has undefined behavior. Are there any other rules about a function modifying an object it has received through a pointer with const? There are. The left operand of an assignment operator must be modifiable. C 2018 6.5.16 2 says:
An assignment operator shall have a modifiable lvalue as its left operand.
An lvalue qualified with const is not modifiable, per C 2018 6.3.2.1 1. This paragraph is a constraint in the C standard, which means a C implementation is required to diagnose violations. (So, again, this is not implementation-defined behavior. The C implementation must produce a message.) The ++ and -- operators, both pre- and post-, have similar constraints.
So, a function with a parameter const char s[] cannot directly modify *s or s[i], at least not without getting a diagnostic message. However, a program is allowed to remove const in a conversion operator if it was not originally present. C 2018 6.3.2.3 2 says we can add const:
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.
and then C 2018 6.3.2.3 7 says that, after we have done that, we can convert the const version back to the original type:
A pointer to an object type may be converted to a pointer to a different object type… when converted back again, the result shall compare equal to the original pointer.
What this means is that if a calling routine has:
int x = 3;
foo(&x);
printf("%d\n", x);
and foo is:
void foo(const int *p)
{
* (int *) p = 4;
}
then this is allowed and defined by the C standard. The function foo removes const and modifies the object it points to, and “4” will be printed.
A lesson here is that const in function parameters is advisory, not enforced by C. It serves two purposes:
const on a function parameter is generally an indication for humans that the function will not modify the pointed-to object through that parameter. (However, there are circumstances, not discussed here, where this indication does not hold.)
The compiler will enforce a rule that the pointed-to object cannot be modified through the const type. This prevents inadvertent errors where a typographical error might result in an unwanted assignment to a const object. However, a function is permitted to explicitly remove const and then attempt to modify the object.
That material appears a bit obsolete.
The strlen standard library function returns size_t nowadays, but anyway:
int strlen(const char[]);, which is the same as, int strlen(const char*); means
strlen can accept either a char* or const char* without needing a cast.
If you pass a pointer to a non-const variable and the function attempts to modify it (by casting away the const as in void modify(char const *X){ *(char*)X='x'; }) the behavior is ̶i̶m̶p̶l̶e̶m̶e̶n̶t̶a̶t̶i̶o̶n̶-̶d̶e̶f̶i̶n̶e̶d̶ undefined (it is undefined, not implementation defined in newer C versions).
Undefined behavior means you lose all guarantees about the program.
Implementation defined means the code will do something specific (e.g., abort, segfault, do nothing) in a consistent, predictable fashion (given a platform) and that it that won't affect the integrity of the rest of the program.
In older simple-minded compilers, a static-lifetime const-variable would be placed in read-only memory if the platform has read-only memory or in writable memory otherwise. Then you either get a segfault on an attempt to modify it or you won't. That would be implementation-defined behavior.
Newer compilers may additionally attempt to do far-reaching optimization based on the const annotations, so it's hard to tell what effects an attempt at such a modification attempt may have. The fact that such a modification attempt is undefined behavior in modern C allows compilers to make such optimizations (i.e., optimizations which yield hardly predictable results if the rule is broken).
const char[] as parameter type of a function is in fact equal to a pointer to const char (type const char *). The array notation was invented for convenience when passing pointer to arrays.
Related posts:
C pointer notation compared to array notation: When passing to function
Passing an array as an argument to a function in C
With declaring const char p_a[] you declare p_a as a pointer to const char.
The const qualifier associated to char tells the compiler that the char object/array pointed to in the caller shouldn't be modified.
Any attempt to modify this object/array with a non-const pointer/lvalue invokes undefined behavior:
"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."
Source: C18, 6.7.3/7
and a compiler usually will warn you when doing so.
strlen does not need to and also should not modify a string to which a pointer is passed as argument. For the sake of not giving any chance to accidentally modifying the string in the caller, the pointer is classified as pointer to const char.
The const qualifier adds an extra layer of security.
With this definition:
struct vector {
const float x;
const float y;
};
would the code snippet below possibly result in undefined behavior?
struct vector src = {.x=1.0, .y=1.0};
struct vector dst;
void *dstPtr = &dst;
memcpy(dstPtr, &src, sizeof dst);
gcc and clang do not emit any warnings, but it does result in modification of a const-qualified type.
The construct looks a lot like the one given in the accepted answer to How to initialize const members of structs on the heap
, which apparently is conformant. I do not understand how my example would therefore be non-conformant.
The const-qualifiers on members let the compiler assume that - after an object has been initialized - these members must not be altered through any way, and it may optimise code accordingly (cf, for example, #Ajay Brahmakshatriya comment).
So it is essential to distinguish the initialization phase from the subsequent phases where assignments would apply, i.e. from when on may a compiler assume that an object got initialized and has an effective type to rely on.
I think there is a main difference between your example and that in the accepted answer you cited. In this SO answer, the target aggregate object with const-qualified member types is created through malloc:
ImmutablePoint init = { .x = x, .y = y };
ImmutablePoint *p = malloc(sizeof *p);
memcpy(p, &init, sizeof *p);
According to the rules on how a stored value of an object may be accessed (cf. this part of an online c standard draft), the p-target object will get its effective type the first time in the course of performing memcpy; the effective type is then that of the source object init, and the first memcpy on an object that got malloced can be seen as an initialization. Modifying the target object's const members afterwards, however, would be UB then (I think that even a second memcpy would be UB, but that's probably opinion based).
6.5 Expressions
The effective type of an object for an access to its stored value is the declared type of the object, if any.87) ... If a value is
copied into an object having no declared type using memcpy or memmove,
or is copied as an array of character type, then the effective type of
the modified object for that access and for subsequent accesses that
do not modify the value is the effective type of the object from which
the value is copied, if it has one. For all other accesses to an
object having no declared type, the effective type of the object is
simply the type of the lvalue used for the access.
87) Allocated objects have no declared type.
In your example, however, the target object dst already has a declared type through it's definition struct vector dst;. Hence, the const-qualifiers on dst's members are already in place before the memcpy is applied, and it has to be seen as an assignment rather than an initialization.
So I'd vote for UB in this case.
Does the following code invoke undefined behavior (due to aliasing violation or otherwise)?
int foo(int (*a)[10], int (*b)[5])
{
(*a)[5]++;
return (*b)[0];
}
int x[10];
foo(&x, (int (*)[5])&x[5]);
Note that the corresponding code using plain int * rather than pointer-to-array types would be perfectly legal, because a and b would be pointers to the same type and thus allowed to alias one another.
Edit: The interesting consequence, if this is in fact an aliasing violation, is that it seems to be a hackish but valid way to get restrict semantics pre-C99. As in:
void some_func(int *aa, int *bb)
{
int (*a)[1] = (void *)aa;
int (*b)[2] = (void *)bb;
/* Now **a and **b can be assumed by the compiler not to alias */
}
Presumably if you needed to access an actual array at each address, you could use SIZE_MAX-1 and SIZE_MAX-2 etc. as the differing sizes.
You are not accessing objects through pointers of different type here: you are not manipulating the array objects to which a and b point themselves but the objects pointed to by (*a)+5 and (*b)+0, namely *((*a)+5) and *((*b)+0). Since these are pointers to the same type they may well alias to the same object.
The implicit assignment by the ++ operator is a valid assignment to the object pointed to by (*b)+0: ++ is equivalent to x = x + 1 (besides x being evaluated only once) and for simple assignment = the standard says
If the value being stored in an object is read from another object
that overlaps in any way the storage of the first object, then the
overlap shall be exact and the two objects shall have qualified or
unqualified versions of a compatible type; otherwise, the behavior is
undefined.
The types here are exactly the same and the overlap is exact.
I have the following piece of code:
void TestFunc(const void * const Var1, const float Var2)
{
*(float*)Var1 = Var2;
}
It looks like I am changing the value of the const object the const pointer points to (thanks sharptooth), which should not be allowed. Fact is, none of the compilers I tried issued a warning. How is this possible?
As others mentioned, the cast removes the 'constness' of the destination as far as the expression is concerned. When you use a cast the compiler treats the expression according to the cast - as long as the cast itself it valid (and C-style casts are pretty much the big hammer). This is why you don't get an error or warning. You're essentially telling the compiler, "be quiet, I know what I'm doing, this is how you should treat things". In fact, casts are probably the #1 way for programmers to get the compiler to stop issuing warnings.
Your assignment expression may or may not be undefined behavior. It is permitted to cast away constness if the object actually pointed to is not const.
However, if the object pointed to is const, then you have undefined behavior.
void TestFunc(const void * const Var1, const float Var2)
{
*(float*)Var1 = Var2;
}
int
main(void)
{
float x = 1.0;
const float y = 2.0;
TestFunc( &x, -1.0); // well defined (if not particularly great style)
TestFunc( &y, -2.0); // undefined behavior
return 0;
}
You're treading dangerous waters...
In general (I'm sure there are exceptions), casting so that expressions treat objects as they really are is supported, well-defined behavior in C/C++.
This particular behavior is covered in the standards mostly by statements that modifying a const object through a cast (or something) that removes the const qualifier is undefined. The inference is that doing the same for a non-const object is not undefined. An example given in the C++ standard makes this clear.
C90 6.5.3 - Type Qualifiers (C99 6.7.3):
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.
C++ 7.1.5.1 The cv-qualifiers
A pointer or reference to a cv-qualified type need not actually point or refer to a cv-qualified object, but it is treated as if it does; a const-qualified access path cannot be used to modify an object even if the object referenced is a non-const object and can be modified through some other access path. [Note: cv-qualifiers are
supported by the type system so that they cannot be subverted without casting (5.2.11). ]
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const
object during its lifetime (3.8) results in undefined behavior.
...
[Example:
...
int i = 2; //not cv-qualified
const int* cip; //pointer to const int
cip = &i; //OK: cv-qualified access path to unqualified
*cip = 4; //ill-formed: attempt to modify through ptr to const
int* ip;
ip = const_cast<int*>(cip); //cast needed to convert const int*to int*
*ip = 4; //defined: *ip points to i, a non-const object
const int* ciq = new const int (3); //initialized as required
int* iq = const_cast<int*>(ciq); //cast required
*iq = 4; //undefined: modifies a const object
You change the object the pointer points to, not the pointer value.
C-style cast acts like a const_cast and removes the const modifier off the pointer. The compiler now has nothing to moan about.
The cast is legal, but the behaviour is undefined.