我有很多类似的代码:
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();
但是现在我有了更复杂的结构和指向它们的指针:
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();
信不信由你,这仍然过于简单现在很明显我可以使用volatile,我想我可以这样做来强制重新加载:
if(*(volatile int *)&peg->x == 15)
这能保证换装吗换句话说,如果(peg->x)知道一个volatile cast已经“重新加载”了变量,那么我以后是否能够简单地编写?
问题是速度,因为我的函数可以被不断调用,数百万次当然比上面复杂得多我想知道如果回调信号修改了变量,是否有必要或者更可取,是否有某种方法来处理这个问题。我正在处理结构中的几十个变量,除非有必要,否则我不希望它们被“重新加载”。
另外,C99标准是否对我的两个伪样本中的任何一个有洞察力,例如,保证在函数之后变量被“重新加载”(我不知道正确的单词)或者这个问题是编译器和优化级别特定的吗我用gcc在-O0和-O3测试了一些其他类似的稀释样品,在两种情况下都没有发现差异(在所有情况下变量都有其适当的值)。
谢谢大家!
编辑11/24/2010下午1点东部时间:为了解决我所说的“重新加载”的问题,我的意思是如果编译器在函数调用之前缓存变量(例如在寄存器或其他内存空间中),那么在函数调用之后它仍然会访问同一个缓存变量,而不是(可能)更新的变量。
此外,编译器还可以循环提升以从循环中移除未更改的变量例如,如果每次循环中都有peg->x而不是access peg->x,编译器是否可以确定没有其他访问,即使传递了回调&peg如果我有这样的代码:
peg = ⪚
while(1)
{
if(!peg->x)
if(callback)
callback(peg);
if(!peg->x)
peg->x = 20;
if(peg == &eg)
peg = &eg2;
else
break;
}
编译器是否可以像这样优化它例如:
while(1)
{
if(!peg->x)
{
if(callback)
callback(peg);
peg->x = 20;
}
if(peg == &eg)
peg = &eg2;
else
break;
}
或者可以这样优化它:
{
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;
}
编辑11/24/2010东部时间下午1:45:这里是另一个例子回调可以更改指向自身的指针。
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);
}
最佳答案
只要不调用任何未定义的行为,编译器就不允许跨可能修改变量的函数调用“缓存”该变量的值在这种情况下,未定义行为的特别相关实例是:
修改声明为const
的变量;以及
通过非unsigned char
的不兼容类型的左值修改变量。
例如,如果你有:
void modifier(int *a)
{
*a += 10;
}
那么这将始终有效:
int i = 5;
modifier(&i);
if (i == 15)
puts("OK");
但是,这可能不会:
const int i = 5;
modifier((int *)&i);
if (i == 15)
puts("OK");
这同样适用于更复杂的例子如您的示例所示,确定函数是否可以修改给定变量所需的静态分析可能非常复杂在这种情况下,编译器必须是保守的,这通常意味着在实践中,一旦获取了变量的地址,可以围绕该变量进行的优化就非常有限。
附录:
这是C标准所涵盖的,但它并没有采取(徒劳的)方法,试图列出每一个可能的优化,并宣布是否允许相反,C标准规定(§5.1.2.3 in C99):
本文中的语义描述
国际标准描述
抽象机器在
哪些优化问题是
无关紧要。
换句话说,C标准描述了一个以简单方式工作的抽象机,任何优化都必须确保在C抽象机上正确运行的任何程序在这些优化下也能正确运行。
修改对象(即使是通过函数调用中的深度嵌套指针)也是一种副作用。在C抽象机中,所有的副作用都在下一个序列点完成(在每个表达式的末尾都有一个序列点,例如在调用示例中的
callback()
之后)。这在第5.1.2.3节中也有详细说明由于C抽象机中的对象一次只有一个值,因此在修改完成之后,对该对象的任何后续读取都必须读取新值。因此,在C抽象机下不需要任何
ignore_any_variables_previously_in_registers()
-在寄存器(或者实际上是“寄存器”)中没有缓存变量的概念。如果您知道存储在嵌套结构中的某个特定变量不会被函数调用修改,并且您希望允许它在该函数调用中被缓存,那么只需创建一个本地变量来显式缓存它—与
someregister
中的示例完全相同如果合适,编译器将使用someregister
的寄存器。为了解决您的新示例,编译器不允许执行您建议的优化。如果它对分配给函数指针
->callback
的函数一无所知,则必须假定它可以更改地址可能与->callback()
可用指针别名的任何变量。实际上,这往往意味着只有地址未被取下的局部变量才能被认为是安全的。在C99中引入了restrict
关键字,以允许程序员进一步向优化者保证此类别名。