F
Frank Wallingford
Note: For those with instant reactions, this is NOT the common "why is i
= i++ not defined?" question. Please read on.
I came across an interesting question when talking with my colleagues.
According to the standard, most operators evaluate their operands in an
unspecified order. This means that in code like this:
f() + g()
the two functions may be called in either order, and still be standards-
compliant.
Fine. Now, when the evaluation of one sub-expression begins, is there
anything in the standard that says that it must complete (ignoring side-
effects) before the other sub-expression evaluation begins? In other
words, can pieces of the sub-expressions be evaluated in an interleaving
fashion?
Take this example:
f(i++) + g(i++)
The difference from the first example is the sequence points for the
function calls.
Now, obviously since f() and g() may be called in any order, this code
is non-deterministic. My question is: can the compiler evaluate the sub-
expressions in the following order, interleaving the sub-sub-expression
evaluations?
t1 = i;
t2 = i;
i++;
i++;
f(t1);
g(t2);
Note that all side-effects were finished before the function-call
sequence points, so this doesn't violate that.
If there is nothing in the standard to prevent this, then not only is
the code "f(i++) + g(i++)" non-deterministic, but it remains as
undefined as "i++ + i++".
Here is an example where I think the above problem exists, although it's
not obvious (please ignore the obvious problems and poor design; this is
pseudo-code to illustrate a point):
const char *a(void)
{
static char *x;
if (x) free(x);
x = (char*)malloc(2);
strcpy(x, "a");
return x;
}
printf("%s %s\n", a(), a());
The obvious problem here is that the second call to a() (in either
order) will free the pointer returned by the first call.
Here is the revised version, but I believe that the following is still
undefined:
const char *a(void) { ... same as above ... }
static list *strings; // implementation not important
const char *cache(const char *in)
{
char *out;
out = strdup(in);
add_to_list(out);
return out;
}
printf("%s %s\n", cache(a()), cache(a()));
The cache() function only exists so that calls like 'printf' can be made
- that is, they copy the argument off and return the copy (and save it
in a list to be freed later). This way, the second call to a() doesn't
affect anything by freeing the old value.
But, similar to the above, I believe that an implementation is free to
evaluate the printf call in this order:
char *t1 = a();
char *t2 = a();
char *t3 = cache(t1);
char *t4 = cache(t2);
printf("%s %s\n", t3, t4);
Is this right? Is it undefined, according to the standard?
-Frank
= i++ not defined?" question. Please read on.
I came across an interesting question when talking with my colleagues.
According to the standard, most operators evaluate their operands in an
unspecified order. This means that in code like this:
f() + g()
the two functions may be called in either order, and still be standards-
compliant.
Fine. Now, when the evaluation of one sub-expression begins, is there
anything in the standard that says that it must complete (ignoring side-
effects) before the other sub-expression evaluation begins? In other
words, can pieces of the sub-expressions be evaluated in an interleaving
fashion?
Take this example:
f(i++) + g(i++)
The difference from the first example is the sequence points for the
function calls.
Now, obviously since f() and g() may be called in any order, this code
is non-deterministic. My question is: can the compiler evaluate the sub-
expressions in the following order, interleaving the sub-sub-expression
evaluations?
t1 = i;
t2 = i;
i++;
i++;
f(t1);
g(t2);
Note that all side-effects were finished before the function-call
sequence points, so this doesn't violate that.
If there is nothing in the standard to prevent this, then not only is
the code "f(i++) + g(i++)" non-deterministic, but it remains as
undefined as "i++ + i++".
Here is an example where I think the above problem exists, although it's
not obvious (please ignore the obvious problems and poor design; this is
pseudo-code to illustrate a point):
const char *a(void)
{
static char *x;
if (x) free(x);
x = (char*)malloc(2);
strcpy(x, "a");
return x;
}
printf("%s %s\n", a(), a());
The obvious problem here is that the second call to a() (in either
order) will free the pointer returned by the first call.
Here is the revised version, but I believe that the following is still
undefined:
const char *a(void) { ... same as above ... }
static list *strings; // implementation not important
const char *cache(const char *in)
{
char *out;
out = strdup(in);
add_to_list(out);
return out;
}
printf("%s %s\n", cache(a()), cache(a()));
The cache() function only exists so that calls like 'printf' can be made
- that is, they copy the argument off and return the copy (and save it
in a list to be freed later). This way, the second call to a() doesn't
affect anything by freeing the old value.
But, similar to the above, I believe that an implementation is free to
evaluate the printf call in this order:
char *t1 = a();
char *t2 = a();
char *t3 = cache(t1);
char *t4 = cache(t2);
printf("%s %s\n", t3, t4);
Is this right? Is it undefined, according to the standard?
-Frank