Given the following code:
unsigned int global_flag = 0;
void exception_handle()
{
global_flag = 1;
}
void func()
{
/* access will cause exception which will assign global_flag = 1
then execution continues */
volatile unsigned int x = *(unsigned int *)(0x60000000U); /* memory protection unit configured to raise exception upon accessing this address */
if (global_flag == 1)
{
/* some code */
}
}
Given the fact that volatile must not be reordered across sequence points:
The minimum requirement is that at a sequence point all previous
accesses to volatile objects have stabilized and no subsequent
accesses have occurred
And given the following about sequence points:
sequence points occur in the following places ... (1) .. (2) .. (3) At the end of a full expression. This category includes expression
statements (such as the assignment a=b;), return statements, the
controlling expressions of if, switch, while, or do-while statements,
and all three expressions in a for statement.
Is it promised that volatile unsigned int x = *(unsigned int *)(0x60000000U); will take place before if (global_flag == 1) (in the binary asm, the CPU out-of-order execution is not relevant here) ?
According to the citations above, volatile unsigned int x = *(unsigned int *)(0x60000000U); must be evaluated before the end of next sequence point, and volatile unsigned int x = *(unsigned int *)(0x60000000U); is a sequence point by itself, so is that means that every volatile assignment is evaluated at the assignment time?
If the answer to above question is no, than next sequence point is at the end of the if, does it mean that something like that can be executed:
if (global_flag == 1)
{
volatile unsigned int x = *(unsigned int *)(0x60000000U);
/* some code */
}
System is an embedded one- ARM cortex m0, single core, single thread application.
In your snippet the variable global_flag is not volatile, so nothing prevents the compiler from moving the access to global_flag across sequence points or to remove it entirely if circumstances allow it. It does not make sense to talk about the order of the access to x and the access to global_flag because the latter is not an observable event, only the former is.
(Also note that there is no volatile qualifier in the expression *(unsigned int *)(0x60000000U). I think it is really that expression that you wish to treat specially, but your code does not do that. The compiler is allowed to produce code that evaluates *(unsigned int *)(0x60000000U) well in advance, then does a ton of other stuff it has on its plate, then assigns the value that was obtained to x and this would satisfy the constraints that the C standards place on volatile lvalues.)
If your snippet had unsigned int volatile global_flag = 0; and *(volatile unsigned int *)(0x60000000U) then the answer to the question “Is it promised that …” would be an unambiguous “yes”.
Is it promised that volatile unsigned int x = *(unsigned int *)(ILLEGAL_ADDRESS); will take place before if (global_flag == 1)
From informative C11 AnnexC (added newlines/formatting for readability):
The following are the sequence points described in 5.1.2.3:
...
- Between the evaluation of a full expression and the next full expression to be evaluated.
- The following are full expressions:
- an initializer that is not part of a compound literal (6.7.9);
- the expression in an expression statement (6.8.3);
- the controlling expression of a selection statement (if or switch) (6.8.4);
- the controlling expression of a while or do statement (6.8.5);
- each of the (optional) expressions of a for statement (6.8.5.3);
- the (optional) expression in a return statement (6.8.6.4).
As the *(unsigned int *)(ILLEGAL_ADDRESS); is an initializer (assignment expression) and the initializer is not part of a compound literal, it is a full expression. The next full expression is the controlling statement in if, so between if and the initialization of x there is a sequence point.
And from the famous C11 5.1.2.3p6:
The least requirements on a conforming implementation are:
Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.
...
As x is a volatile object, it is initialized strictly to the abstract machine, so after the sequence point it has to have the rvalue equal to the result of *(unsigned int *)(ILLEGAL_ADDRESS) operation.
So yes, the initialization of x object must happen before the control expression inside the if.
On undefined behavior, there's the good quote from C11 6.5.3.2p4:
If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.
As you commented:
accessing address 0x60000000 is not permitted in my system memory model
one can deduce that (unsigned int*)0x60000000 is an invalid pointer, so the unary * operator should spawn dragons.
Related
In C, suppose for a pointer p we do *p++ = 0. If p points to an int variable, is this defined behavior?
You can do arithmetic resulting in pointing one past the end of an "array object" per the standard, but I am unable to find a really precise definition of "array object" in the standard. I don't think in this context it means just an object explicitly defined as an array, because p=malloc(sizeof(int)); ++p; pretty clearly is intended to be defined behavior.
If a variable does not qualify as an "array object", then as far as I can tell *p++ = 0 is undefined behavior.
I am using the C23 draft, but an answer citing the C11 standard would probably answer the question too.
Yes it is well-defined. Pointer arithmetic is defined by the additive operators so that's where you need to look.
C17 6.5.6/7
For the purposes of these operators, a pointer to an object that is not an element of an array behaves
the same as a pointer to the first element of an array of length one with the type of the object as its
element type.
That is, int x; is to be regarded as equivalent to int x[1]; for the purpose of determining valid pointer arithmetic.
Given int x; int* p = &x; *p++ = 0; then it is fine to point 1 item past it but not to de-reference that item:
C17 6.5.6/8
If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation
shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.
This behavior has not changed in the various revisions of the standard. It's the very same from C90 to C23.
There are two separate questions: 1. What constructs does the Standard specify that correct conforming implementations should process meaningfully, and 2. What constructs do clang and gcc actually process meaningfully. The clear intention of the Standard is to define the behavior of a pointer "one past" an array object and a pointer to the start of another array object that happens to immediately follow it. The actual behavior of clang and gcc tells another story, however.
Given the source code:
#include <stdint.h>
extern int x[],y[];
int test1(int *p)
{
y[0] = 1;
if (p == x+1)
*p = 2;
return y[0];
}
int test2(int *p)
{
y[0] = 1;
uintptr_t p1 = 3*(uintptr_t)(x+1);
uintptr_t p2 = 5*(uintptr_t)p;
if (5*p1 == 3*p2)
*p = 2;
return y[0];
}
both clang and gcc will recognize in both functions that the *p=2 assignment will only run if p happens to be equal to a one-past pointer to x, and will conclude as a consequence that it would be impossible for p to equal y. Construction of an executable example where clang and gcc would erroneously make this assumption is difficult without the ability to execute a program containing two compilation units, but examination of the generated machine code at https://godbolt.org/z/x78GMqbrv will reveal that every ret instruction is immediately preceded by mov eax,1, which loads the return value with 1.
Note that the code in test2 doesn't compare pointers, nor even compare integers that are directly formed from pointers, but the fact that clang and gcc are able to show that the numbers being compared can only be equal if the pointers happened to be equal is sufficient for test2() to, as perceived by clang or gcc, invoke UB if the function is passed a pointer to y, and y happens to equal x+1.
An obvious example of undefined behavior (UB), when reading a value, is:
int a;
printf("%d\n", a);
What about the following examples?
int i = i; // `i` is not initialized when we are reading it by assigning it to itself.
int x; x = x; // Is this the same as above?
int y; int z = y;
Are all three examples above also UB, or are there exceptions to it?
Each of the three lines triggers undefined behavior. The key part of the C standard, that explains this, is section 6.3.2.1p2 regarding Conversions:
Except when it is the operand of the sizeof operator, the
_Alignof operator, the unary & operator, the ++ operator, the
-- operator, or the left operand of the . operator or an
assignment operator, an lvalue that does not have array type
is converted to the value stored in the designated object
(and is no longer an lvalue); this is called lvalue
conversion. If the lvalue has qualified type, the value has
the unqualified version of the type of the lvalue; additionally,
if the lvalue has atomic type, the value has the non-atomic version
of the type of the lvalue; otherwise, the value has the
type of the lvalue. If the lvalue has an incomplete type and does
not have array type, the behavior is undefined. If the lvalue
designates an object of automatic storage duration that could
have been declared with the register storage class (never had its
address taken), and that object is uninitialized (not declared
with an initializer and no assignment to it has been
performed prior to use), the behavior is undefined.
In each of the three cases, an uninitialized variable is used as the right-hand side of an assignment or initialization (which for this purpose is equivalent to an assignment) and undergoes lvalue to rvalue conversion. The part in bold applies here as the objects in question have not been initialized.
This also applies to the int i = i; case as the lvalue on the right side has not (yet) been initialized.
There was debate in a related question that the right side of int i = i; is UB because the lifetime of i has not yet begun. However, that is not the case. From section 6.2.4 p5 and p6:
5 An object whose identifier is declared with no linkage and without the storage-class specifier static has automatic
storage duration, as do some compound literals. The result of
attempting to indirectly access an object with automatic storage
duration from a thread other than the one with which the object is
associated is implementation-defined.
6 For such an object that does not have a variable length array type, its lifetime extends from entry into the block
with which it is associated until execution of that block ends in any
way. (Entering an enclosed block or calling a function
suspends, but does not end,execution of the current block.) If
the block is entered recursively, a new instance of the object is
created each time. The initial value of the object is
indeterminate. If an initialization is specified for the
object, it is performed each time the declaration or compound
literal is reached in the execution of the block; otherwise,
the value becomes indeterminate each time the declaration is reached
So in this case the lifetime of i begins before the declaration in encountered. So int i = i; is still undefined behavior, but not for this reason.
The bolded part of 6.3.2.1p2 does however open the door for use of an uninitialized variable not being undefined behavior, and that is if the variable in question had it's address taken. For example:
int a;
printf("%p\n", (void *)&a);
printf("%d\n", a);
In this case it is not undefined behavior if:
The implementation does not have trap representations for the given type, OR
The value chosen for a happens to not be a trap representation.
In which case the value of a is unspecified. In particular, this will be the case with GCC and Microsoft Visual C++ (MSVC) in this example as these implementations do not have trap representations for integer types.
Use of the not initialized automatic storage duration objects invokes UB.
Use of the not initialized static storage duration objects is defined as they are initialized to 0s
int a;
int foo(void)
{
static int b;
int c;
int d = d; //UB
static int e = e; //OK
printf("%d\n", a); //OK
printf("%d\n", b); //OK
printf("%d\n", c); //UB
}
In cases where an action on an object of some type might have unpredictable consequences on platforms where the type has trap representations, but have at-least-somewhat predictable behavior for types that don't, the Standard will seek to avoid distinguishing platforms that do or don't define the behavior by throwing everything into the catch-all category of "Undefined Behavior".
With regard to the behavior of uninitialized or partially-initialized objects, I don't think there's ever been a consensus over exactly which corner cases must be treated as though objects were initialized with Unspecified bit patterns, and which cases need not be treated in such fashion.
For example, given something like:
struct ztstr15 { char dat[16]; } x,y;
void test(void)
{
struct zstr15 hey;
strcpy(hey.dat, "Hey");
x=hey;
y=hey;
}
Depending upon how x and y will be used, there are at least four ways it might be useful to have an implementation process the above code:
Squawk if an attempt is made to copy any automatic-duration object that isn't fully initialized. This could be very useful in cases where one must avoid leakage of confidential information.
Zero-fill all unused portions of hey. This would prevent leakage of confidential information on the stack, but wouldn't flag code that might cause such leakage if the data weren't zero-filled.
Ensure that all parts of x and y are identical, without regard for whether the corresponding members of hey were written.
Write the first four bytes of x and y to match those of hey, but leave some or all of the remaining portions holding whatever they held before test() was called.
I don't think the Standard was intended to pass judgment as to whether some of those approaches would be better or worse than others, but it would have been awkward to write the Standard in a manner that would define behavior of test() while allowing for option #3. The optimizations facilitated by #3 would only be useful if programmers could safely write code like the above in cases where client code wouldn't care about the contents of x.dat[4..15] and y.dat[4..15]. If the only way to guarantee anything about the behavior of that function would be to write all portions of hey were written, including those whose values would be irrelevant to program behavior, that would nullify any optimization advantage approach #3 could have offered.
I have the following C code:
#include <stdint.h>
#include <stdio.h>
int i;
uint64_t a[] = { (uint64_t)&i, (uint64_t)&i + 0x8000000000000000 };
int main() {
printf("%p %llx %llx\n", &i, a[0], a[1]);
}
If I compile this (as C or as C++) with Microsoft Visual Studio Community 2015 and then run it, the output is similar to the following:
013E9154 13e9154 13e9154
It seems that the code + 0x8000000000000000, which I expected to set the high bit of a[1], has been silently ignored.
However, if I move the initialization of a inside main, the output is what I would expect:
00179154 179154 8000000000179154
With a global, why is the addition being silently ignored? Should the attempted addition actually set the high bit of a[1] or should it cause a compiler error?
Interestingly, if + 0x8000000000000000 in the above code is replaced by | 0x8000000000000000, I get "error C2099: initializer is not a constant".
Edit: A similar issue can occur even in the absence of casts. Compiled for x64, the following code prints the same value (e.g. 000000013FB8D180) three times:
#include <stdio.h>
int i;
int * a[] = { &i, &i + 0x100000000 };
int main() {
printf("%p %p %p\n", &i, a[0], a[1]);
}
The initializer
(uint64_t)&i + 0x8000000000000000
isn't a valid constant expression in C. It is neither an arithmetic constant expression which only allows integer constants, floating constants, enumeration constants, character constants, and sizeof expressions as operands; nor an address constant which doesn't allow casts to integer types.
That said, I'd expect Visual Studio to generate "error C2099: initializer is not a constant" like it does with | 0x8000000000000000.
I'm not sure about C++, though.
None of the initializers used in
uint64_t a[] = { (uint64_t)&i, (uint64_t)&i + 0x8000000000000000 };
are eligible constant expressions. The pedantic definition of constant expression in C does not allow casting pointer values to integer types, even if the pointer values satisfies requirements for address constant. Which means that formally (uint64_t)&i is already illegal in this context.
However, this compiler apparently accepts (uint64_t)&i in this context as an extension.
After that the fact that it complains when + is replaced with | operator is probably rooted directly in the language specification
6.6 Constant expressions
7 More latitude is permitted for constant expressions in initializers.
Such a constant expression shall be, or evaluate to, one of the
following:
— an arithmetic constant expression,
— a null pointer constant,
— an address constant, or
— an address constant for an object type plus or minus an integer constant expression.
Again, this is not an exact match, since the above wording allows adding fixed offset to address constants only, but for a compiler that accepts (uint64_t)&i as a constant expression in this context it wouldn't be unusual to continue to apply the "plus or minus" restriction. The ability to add something to (or subtract something from) an address constant in C is defined by the capabilities of loaders that perform address relocation at load time. Loaders can add or subtract, but they cannot perform bitwise operations on addresses.
And, finally, the fact that it has no effect at run time is apparently caused by the limitations of the loader, which is responsible for implementing C-style initialization of statics at startup time.
After hunting for a related or duplicate question concerning the following to no avail (I can only do marginal justice to describe the sheer number of pointer-arithmetic and post-decrement questions tagged with C, but suffice it to say "boatloads" does a grave injustice to that result set count) I toss this in the ring in hopes of clarification or a referral to a duplicate that eluded me.
If the post-decrement operator is applied to a pointer such as below, a simple reverse-iteration of an array sequence, does the following code invoke undefined behavior?
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "some string";
const char *t = s + strlen(s);
while(t-->s)
fputc(*t, stdout);
fputc('\n', stdout);
return 0;
}
It was recently proposed to me that 6.5.6.p8 Additive operators, in conjunction with 6.5.2.p4, Postfix increment and decrement operators, specifies even performing a post-decrement upon t when it already contains the base-address of s invokes undefined behavior, regardless of whether the resulting value of t (not the t-- expression result) is evaluated or not. I simply want to know if that is indeed the case.
The cited portions of the standard were:
6.5.6 Additive Operators
If both the pointer operand and the result point to elements of the
same array object, or one past the last element of the array object,
the evaluation shall not produce an overflow; otherwise, the behavior
is undefined.
and its nearly tightly coupled relationship with...
6.5.2.4 Postfix increment and decrement operators Constraints
The operand of the postfix increment or decrement operator shall have
atomic, qualified, or unqualified real or pointer type, and shall be a
modifiable lvalue.
Semantics
The result of the postfix ++ operator is the value of the operand. As a side effect, the value of the operand object is incremented (that is, the value 1 of the appropriate type is added to it). See the discussions of additive operators and compound assignment for information on constraints, types, and conversions and the effects of operations on pointers. The value computation of the result is sequenced before the side effect of updating the stored value of the operand. With respect to an indeterminately-sequenced function call, the operation of postfix ++ is a single evaluation. Postfix ++ on an object with atomic type is a read-modify-write operation with memory_order_seq_cst memory order semantics.98)
The postfix -- operator is analogous to the postfix ++ operator, except that the value of the operand is decremented (that is, the value 1 of the appropriate type is subtracted from it).
Forward references: additive operators (6.5.6), compound assignment (6.5.16.2).
The very reason for using the post-decrement operator in the posted sample is to avoid evaluating an eventually-invalid address value against the base address of the array. For example, the code above was a refactor of the following:
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "some string";
size_t len = strlen(s);
char *t = s + len - 1;
while(t >= s)
{
fputc(*t, stdout);
t = t - 1;
}
fputc('\n', stdout);
}
Forgetting for a moment this has a non-zero-length string for s, this general algorithm clearly has issues (perhaps not as clearly to some). If s[] were instead "", then t would be assigned a value of s-1, which itself is not in the valid range of s through its one-past-address, and the evaluation for comparison against s that ensues is no good. If s has non-zero length, that addresses the initial s-1 problem, but only temporarily, as eventually this is still counting on that value (whatever it is) being valid for comparison against s to terminate the loop. It could be worse. it could have naively been:
size_t len = strlen(s) - 1;
char *t = s + len;
This has disaster written all over it if s were a zero-length string. The refactored code of this question opened with was intended to address all of these issues. But...
My paranoia may be getting to me, but it isn't paranoia if they're really all out to get you. So, per the standard (these sections, or perhaps others), does the original code (scroll to the top of this novel if you forgot what it looks like by now) indeed invoke undefined behavior or not?
I am pretty certain that the result of the post-decrement in this case is indeed undefined behaviour. The post-decrement clearly subtracts one from a pointer to the beginning of an object, so the result does not point to an element of the same array, and by the definition of pointer arithmetic (§6.5.6/8, as cited in the OP) that's undefined behaviour. The fact that you never use the resulting pointer is irrelevant.
What's wrong with:
char *t = s + strlen(s);
while (t > s) fputc(*--t, stdout);
Interesting but irrelevant fact: The implementation of reverse iterators in the standard C++ library usually holds in the reverse iterator a pointer to one past the target element. This allows the reverse iterator to be used normally without ever involving a pointer to "one before the beginning" of the container, which would be UB, as above.
I seem to have a reasonable understanding of volatiles in general, but there's one seemingly obscure case, in which I'm not sure how things are supposed to work per the standard. I've read the relevant parts of C99 and a dozen or more related posts on SO, but can't find the logic in this case or a place where this case is explained.
Suppose we have this piece of code:
int a, c;
volatile int b;
a = b = 1;
c = b += 1; /* or equivalently c = ++b; */
Should a be evaluated like this:
b = 1;
a = b; // volatile is read
or like this:
b = 1;
a = 1; // volatile isn't read
?
Similarly, should c be evaluated like this:
int tmp = b;
tmp++;
b = tmp;
c = b; // volatile is read
or like this:
int tmp = b;
tmp++;
b = tmp;
c = tmp; // volatile isn't read
?
In simple cases like a = b; c = b; things are clear. But how about the ones above?
Basically, the question is, what exactly does "expression has the value of the left operand after the assignment" mean in 6.5.16c3 of C99 when the object is volatile?:
An assignment operator stores a value in the object designated by the
left operand. An assignment expression has the value of the left operand
after the assignment, but is not an lvalue.
Does it imply an extra read of the volatile to produce the value of the assignment expression?
UPDATE:
So, here's the dilemma.
If "the value of the object after the assignment" is not obtained from the extra read of the volatile object, then the compiler makes the assumption that the volatile object b:
is capable of holding an arbitrary int value that gets written into it, which it may not be (say, bit 0 is hardwired to 0, which is not an unusual thing with hardware registers, for which we are supposed to use volatiles)
cannot change between the point when the assigning write has occurred and the point when the expression value is obtained (and again it can be a problem with hardware registers)
And because of all that, the expression value, if not obtained from the extra read of the volatile object, does not yield the value of the volatile object, which the standard claims should be the case.
Both of these assumptions don't seem to fit well with the nature of volatile objects.
If, OTOH, "the value of the object after the assignment" is obtained from the extra implied read of said volatile object, then the side effects of evaluating assignment expressions with volatile left operands depend on whether the expression value is used or not or are completely arbitrary, which would be an odd, unexpected and poorly documented behavior.
C11 clarifies that this is unspecified.
You can find the final draft of C11 here. The second sentence you quoted now refers to footnote 111:
An assignment operator stores a value in the object designated by the left operand. An assignment expression has the value of the left operand after the assignment,111) but is not an lvalue.
Footnote 111 says this:
The implementation is permitted to read the object to determine the value but is not required to, even when the object has volatile-qualified type.
From common sense I'd argue like this:
If b = (whatever) and whatever can be stored in a register, there's no reason for the compiler to re-evaluate the expression for assignment.
Also because it cannot be more recent than the value in the register.
Consider f(x) vs. r = f(x): Once the result of f(x) is known, it can be assigned.
So for a = b = 1 there should be no reason for assigning 1 to b a second time, just to be able to assign to a.
Also assume you write a = ++b:
Obviously b cannot be incremented a second time; otherwise basic C semantics would be broken.