|
Replying to the code [discussed deeper in this sub-thread]: struct blob { uint16_t a[100]; } x,y,z;
void test2(void)
{
int indices[] = {1,0};
{
int* dat = indices;
int n = 2;
{
struct blob temp;
for(int i=0; i<n; i++)
temp.a[dat[i]] = i; // This is what I'd meant
x=temp;
y=temp;
}
z=x;
}
The rewrite sequence I would envision would be: struct blob { uint16_t a[100]; } x,y,z;
void test2(void)
{
int indices[] = {1,0};
{
int* dat = indices;
int n = 2;
{
struct blob temp1 = x; // Allowed initial value
struct blob temp2 = y; // Allowed initial value
for(int i=0; i<n; i++)
{
temp1.a[dat[i]] = i;
temp2.a[dat[i]] = i;
}
x=temp1;
y=temp2;
}
z=x;
}
Compilers may replace an automatic object whose address is not observable with two objects, provided that anything that is written to one will be written to the other before the latter is examined (if it ever is). Such a possibility is the reason why automatic objects which are written between "setjmp" and "longjmp" must be declared "volatile".If one allows a compiler to split "temp" into two objects without having to pre-initialize the parts that hold Indeterminate Value, that may allow more efficient code generation than would be possible if either "temp" was regarded as holding Unspecified Value, or if copying a partially-initialized object as classified as "modern-style Undefined Behavior", making it necessary for programmers to manually initialize entire structures, including parts whose values would otherwise not observably affect program execution. The optimization benefits of attaching loose semantics to objects of automatic duration whose address is not observable are generally greater than the marginal benefits of attaching those semantics to all objects. The risks, however, are relatively small since everything that could affect the objects would be confined to a single function (it an object's address is passed into another function, its address would be observable during the execution of that function). BTW, automatic objects whose address isn't taken have behaved somewhat more loosely than static objects even in compilers that didn't optimized aggressively. Consider, for example: volatile unsigned char x,y;
int test(int dummy, int mode)
{
register unsigned char result;
if (mode & 1) result = x;
if (mode & 2) result = y;
return result;
}
On many machines, if an attempt to read an uninitialized automatic object whose address isn't taken is allowed to behave weirdly, the most efficient possible code for this function would allocate an "int"-sized register for "result", even though it's only an 8-bit type, do a sign-extending load from `x` and/or `y` if needed, and return whatever happens to be in that register. That would not be a complicated optimization; in fact, it's a simple enough optimization that even a single-shot compiler might be able to do it. It would, however, have the weird effect of allowing the uninitialized "result" object of type "unsigned char" to hold a value outside the result 0..255.Should a compiler be required to initialize "result" in that situation, or should programmers be required to allow for the possibility that if they don't initialize an automatic object it might behave somewhat strangely? |
> Compilers may replace an automatic object whose address is not observable with two objects,
That makes sense.
> do a sign-extending load from `x` and/or `y`
I assume you mean zero-extending; otherwise `x=255` would result in `result=-1`, which is clearly wrong.
> Should a compiler be required to initialize "result" in that situation, or should programmers be required to allow for the possibility that if they don't initialize an automatic object it might behave somewhat strangely?
Of course not. Result (assuming mode&3 == 0) is undefined, and behaviour characteristic of the environment is that result (aka eg eax) can hold any (say) 32-bit value (whether that's 0..FFFF'FFFF or -8000'0000..7FFF'FFFF depends on what operations are applied, but `int` suggests the latter).
None of this involves that the compiler infering objective (and frequently false) properties of the input program (such as "this loop will terminate" or "p != NULL"), though.