开发者

C question: If I pass the address of a variable to a function that modifies it is there a guarantee that the variable will be "reloaded" after return?

I have a lot of code that used to be similar to this:

int num = 15;  

if(callback)  
  callback(&num);  /* this function may or may not change the value of num */  

if(num == 15)  /* I assumed num did not need to be volatile and is reloaded */  
  do_something();  
else  
  do_something_else();  

However I now have more complicated structs and pointers to them:

struct example { int x,y,z; };  
struct example eg, eg2, *peg;   
int whatever;  
char fun;  

struct variables { struct example **ppeg; int *whatever; char *fun; };  
struct variables myvars;  

peg = ⪚  
myvars.ppeg = &peg;  
myvars.whatever = &whatever;  
myvars.fun = &fun;  
[...]  
peg->x = 15;  

if(callback)  
  callback(&myvars);  /* this function may or may not change the variables */  

if(peg->x == 15)  /* Can I assume that x is "reloaded" ? */  
  do_something();  
else  
  do_something_else();  

Believe it or not this is still overly simplified. Now obviously I could use volatile, I assume I could do something like this to force a reload:

if(*(volatile int *)&peg->x == 15)  

Does that guarantee a reload? In other words would I then later be able to write simply if(peg->x) knowing that the one volatile cast had already "reloaded" the variable?

The issue is speed as my function could be called continually, millions of times. It is of course much much more intricate than above. I wonder if it would be necessary or preferable to have the callback signal if it modifies the variables, and if there would be some way to handle that. I'm dealing with dozens of variables in the struct and I don't want them to be "reloaded" unless necessary.

Also, does the C99 standard have insight on either of my two pseudo samples, for example to guarantee that after a function that the variables are "reloaded" (I don't know the right word). Or is this issue compiler and optimization level specific? I did test some other similar watered down samples with gcc at -O0 and -O3 and I did not see a difference in either case (in all cases variables had their proper value).

Thanks everyone!

EDIT 11/24/2010 1PM EST: To address the comments on what I mean by "reloading" I meant if the compiler is caching the variable (in a register or other memory space for example) before the function call will it still access the same cached variable after the function call, rather than the (possibly) updated variable.

Also compilers can loop hoist to remove variables from the loop that do not change. Like if I have peg->x, rather than access peg->x each time in the loop could the compiler determine there are no other accesses even if the callback is passed &peg? If I have code like this:

peg = ⪚  
while(1)  
{  
  if(!peg->x)  
    if(callback)  
      callback(peg);  

  if(!peg->x)  
    peg->x = 20;  

  if(peg == &eg)  
    peg = &eg2;  
  else  
    break;  
}  

So could the compiler optimize it like this for example:

开发者_运维问答
while(1)  
{  
  if(!peg->x)  
  {  
    if(callback)  
      callback(peg);  

    peg->x = 20;  
  }  

  if(peg == &eg)  
    peg = &eg2;  
  else  
    break;  
}  

or could it optimize it like this:

{  
  int someregister;  
  peg = ⪚  
  someregister = peg->x;  

  if(!someregister)  
    if(callback)  
      callback(peg);  

  if(!someregister)  
    peg->x = 20;  

  peg = &eg2;  
  someregister = peg->x;  

  if(!someregister)  
    if(callback)  
      callback(peg);  

  if(!someregister)  
    peg->x = 20;  
}  

EDIT 11/24/2010 1:45PM EST: Here's another example. The callback could change the pointer to itself.

if(psomestruct->callback)  
{  
  psomestruct->callback(psomestruct); /* this callback could change the pointer to itself */  
  if(psomestruct->callback) /* will the compiler optimize this statement out? */  
    psomestruct->callback(psomestruct);  
}  


The compiler is not allowed to "cache" the value of a variable across function calls that may modify it, as long as you don't invoke any undefined behaviour. Particularly relevant instances of undefined behaviour in this context are:

  • modifying a variable that was declared const; and
  • modifying a variable through an lvalue of incompatible type that is not unsigned char.

So, for example, if you have:

void modifier(int *a)
{
    *a += 10;
}

then this will always work:

int i = 5;
modifier(&i);
if (i == 15)
    puts("OK");

However, this may not:

const int i = 5;
modifier((int *)&i);
if (i == 15)
    puts("OK");

The same applies to your more complicated examples. As your examples show, the static analysis required to determine if a function can or cannot modify a given variable can be quite complex. Compilers are required to be conservative in this case, which often means in practice that once you have taken the address of a variable, the optimisations that can be done around that variable are quite limited.


Addendum:

This is covered by the C standard, but it does not take the (futile) approach of trying to list every possible optimisation and declare whether it is allowed or disallowed. Instead, the C standard says (§5.1.2.3 in C99):

The semantic descriptions in this International Standard describe the behavior of an abstract machine in which issues of optimization are irrelevant.

In other words, the C standard describes an abstract machine that works in a straightforward manner, and any optimisations must ensure that any program that runs correctly on the C abstract machine also run correctly under those optimisations.

Modifying an object (even through a deeply nested pointer within a function call) is a side-effect. In the C abstract machine, all side-effects are complete by the next sequence point (there is a sequence point at the end of every expression, for example after the call to callback() in your example). This is also detailed in §5.1.2.3. Since objects in the C abstract machine have only one value at one time, after the modification is complete any subsequent reads of the object must read the new value.

Thus there is no need for any ignore_any_variables_previously_in_registers() - under the C abstract machine, there is no concept of caching variable in registers (or indeed, "registers" at all).

If you know that a particular variable stored deeply within your nested structs is not modified by the function call, and you want to allow it to be cached across that function call, simply create a local variable to explicitly cache it - exactly as per your example with someregister. The compiler will use a register for someregister if it is appropriate.

To address your new examples, the compiler is not allowed to perform those optimisations you suggest. If it knows nothing about the function assigned to the function pointer ->callback, it must assume that it can change any variable whose address is possibly aliased with a pointer available to ->callback(). In practice, this tended to mean that only local variables whose address had not been taken could be assumed to be safe. The restrict keyword was introduced in C99 to allow the programmer to make further guarantees to the optimiser about such aliasing.


There is no such thing as "reloading" a variable. You can omit the volatile, you will still get what you want.

I think you are seeing a problem where none exists.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜