我遇到过跨平台代码在基本赋值语句上表现不同的情况。
一个编译器首先评估Lvalue,然后评估Rvalue,然后评估分配。
另一个编译器首先执行Rvalue,然后执行Lvalue,然后执行赋值。
如果Lvalue影响Rvalue的值,这可能会产生影响,如下例所示:
struct MM {
int m;
}
int helper (struct MM** ppmm ) {
(*ppmm) = (struct MM *) malloc (sizeof (struct MM));
(*ppmm)->m = 1000;
return 100;
}
int main() {
struct MM mm = {500};
struct MM* pmm = &mm
pmm->m = helper(&pmm);
printf(" %d %d " , mm.m , pmm->m);
}
上面的例子,行pmm->m = helper(&mm);
,取决于评估的顺序。如果首先评估左值,则pmm-> m等于mm.m,并且如果首先计算的Rvalue大于pmm-> m,则等于在堆上分配的MM实例。
我的问题是,是否有一个C标准来确定评估的顺序(没有找到任何),或者每个编译器都可以选择做什么。 还有其他类似的陷阱我应该知道吗?
答案 0 :(得分:4)
评估for ( int i = ArraySize( symbolCache.HD ) - 1;
i >= 0;
--i )
{ if ( symbolCache.HD[i].number != CopyRates( symbolCache._symbol,
symbolCache.HD[i].TF,
indexation,
symbolCache.HD[i].number,
symbolCache.HD[i].bars
)
)
{
Print( symbolCache._symbol, ", ", symbolCache.HD[i].TF, ", ", symbolCache.HD[i].number, ", " );
return( false );
}
q += " CREATE TABLE IF NOT EXISTS bridge_" + symbolCache.symbol + "_" + symbolCache.HD[i].strTF + "(open_date_time timestamp, date_time timestamp PRIMARY KEY, close_date_time timestamp, open_price DOUBLE PRECISION, high_price DOUBLE PRECISION, low_price DOUBLE PRECISION, close_price DOUBLE PRECISION, volume DOUBLE PRECISION); ";
q2 += " CREATE TABLE IF NOT EXISTS bar_" + symbolCache.symbol + "_" + symbolCache.HD[i].strTF + "(open_date_time timestamp, date_time timestamp PRIMARY KEY, close_date_time timestamp, open_price DOUBLE PRECISION, high_price DOUBLE PRECISION, low_price DOUBLE PRECISION, close_price DOUBLE PRECISION, volume DOUBLE PRECISION); ";
Print( "Created " + IntegerToString( PSQL_Query( idDB, q ) ) + ", " + q );
Print( "Created2 " + IntegerToString( PSQL_Query( idDB2, q2 ) ) + ", " + q2 );
int barSize = ArraySize( symbolCache.HD[i].bars );
Print( "barSize = ", barSize );
for ( int j = 0; j < barSize; ++j )
{
// Print( "j = ", j );
q = "";
q2 = "";
q += " INSERT INTO bridge_"
+ symbolCache.symbol + "_"
+ symbolCache.HD[i].strTF + " VALUES ("
+ TimeToStr( symbolCache.HD[i].bars[j].time, TIME_DATE|TIME_MINUTES|TIME_SECONDS ) + "', '"
+ TimeToStr( symbolCache.HD[i].bars[j].time, TIME_DATE|TIME_MINUTES|TIME_SECONDS ) + "." + IntegerToString( GetTickCount() % 1000 ) + "', '"
+ TimeToStr( symbolCache.HD[i].bars[j].time + symbolCache.HD[i].TF * 60, TIME_DATE|TIME_MINUTES|TIME_SECONDS ) + "', "
+ DoubleToStr( symbolCache.HD[i].bars[j].open, symbolCache._digits ) + ", "
+ DoubleToStr( symbolCache.HD[i].bars[j].high, symbolCache._digits ) + ", "
+ DoubleToStr( symbolCache.HD[i].bars[j].low, symbolCache._digits ) + ", "
+ DoubleToStr( symbolCache.HD[i].bars[j].close, symbolCache._digits ) + ", "
+ IntegerToString( symbolCache.HD[i].bars[j].tick_volume )
+ ");";
Print( "First :" + IntegerToString( PSQL_Query( idDB, q ) ) + " iter : " + IntegerToString( j ) + ", q : " + q );
q2 += " INSERT INTO bar_"
+ symbolCache.symbol + "_"
+ symbolCache.HD[i].strTF + " VALUES ('"
+ TimeToStr( symbolCache.HD[i].bars[j].time + off, TIME_DATE|TIME_MINUTES|TIME_SECONDS ) + "', '"
+ TimeToStr( symbolCache.HD[i].bars[j].time + off, TIME_DATE|TIME_MINUTES|TIME_SECONDS ) + "." + IntegerToString( GetTickCount() % 1000 ) + "', '"
+ TimeToStr( symbolCache.HD[i].bars[j].time + off + symbolCache.HD[i].TF * 60, TIME_DATE|TIME_MINUTES|TIME_SECONDS ) + "." + IntegerToString( GetTickCount() % 1000 ) + "', "
+ DoubleToStr( symbolCache.HD[i].bars[j].open, symbolCache._digits ) + ", "
+ DoubleToStr( symbolCache.HD[i].bars[j].high, symbolCache._digits ) + ", "
+ DoubleToStr( symbolCache.HD[i].bars[j].low, symbolCache._digits ) + ", "
+ DoubleToStr( symbolCache.HD[i].bars[j].close, symbolCache._digits ) + ", "
+ IntegerToString( symbolCache.HD[i].bars[j].tick_volume )
+ ");";
Print( "Sec : " + IntegerToString( PSQL_Query( idDB2, q2 ) ) */ + " iter : " + IntegerToString( j ) + ", q : "+ q2 );
}
}
表达式的语义包括
在左右操作数的值计算之后,对更新左操作数的存储值的副作用进行排序。 操作数的评估未被排除。
(C2011,6.5.16 / 3;重点补充)
强调条款明确允许您在不同编译器编译时观察到程序行为的差异。此外,未序列意味着,即使在程序的相同构建的不同运行中,也允许评估以不同的顺序发生。如果出现未序列评估的函数被多次调用,那么在同一程序执行中的不同调用期间,允许评估以不同的顺序发生。
那已经回答了这个问题,但重要的是要看到更大的图景。修改对象或调用执行此操作的函数是副作用(C2011,5.1.2.3 / 2)。因此,这项关键条款发挥作用:
如果对标量对象的副作用相对于同一标量对象的不同副作用或使用相同标量对象的值进行的值计算未被排序,则行为未定义。
(C2011,6.5 / 2)
被调用函数具有修改存储在=
变量main()
中的值的副作用,对赋值左手操作数的求值涉及使用值{{{ 1}},这些都没有排序,因此行为未定义。
不惜一切代价避免未定义的行为。由于您的程序行为未定义,因此不限于您观察到的两种替代方案(如果不够糟糕)。 C标准对其可能做的事情没有任何限制。它可能会崩溃,将您的硬盘驱动器的分区表归零,或者,如果您有合适的硬件,则召唤鼻子恶魔。或其他任何东西。其中大部分都不太可能,但最好的观点是,如果您的程序有未定义的行为,那么您的程序错误。
答案 1 :(得分:2)
使用简单赋值运算符:=
时,未指定操作数的评估顺序。评估之间也没有序列点。
例如,如果您有两个功能:
*Get() = logf(2.0f);
没有指定它们在任何时候被调用的顺序,但这种行为是完全定义的。
函数调用将引入序列点。它将在评估参数之后和实际调用之前发生。运算符;
也将引入序列点。这很重要,因为在没有插入序列点的情况下,对象不能被修改两次,否则行为是未定义的。
由于未指定的行为,您的示例特别复杂,并且可能会有不同的结果,具体取决于首先评估左或右操作数。
评估左操作数,指针pmm
将指向结构mm
。然后调用该函数,并出现一个序列点。它通过将指针指向已分配的内存来修改指针pmm
,然后由于运算符;
而指向序列点。然后它将值1000存储到成员m
,然后由于;
而存储另一个序列点。该函数返回100并将其分配给左操作数,但由于左操作数首先被计算,值100,因此它被分配给对象mm
,更具体地说是其成员m
。
mm->m
的值为100,ppm->m
的值为1000.这是定义的行为,在序列点之间没有对象被修改两次。
首先调用该函数,发生序列点,它通过将指针指向新分配的结构,然后是序列点来修改指针ppm
。然后它将值1000存储到成员m
,然后是序列点。然后函数返回。然后评估左操作数,ppm->m
将指向新分配的结构,并通过为其赋值100来修改其成员m
。
mm->m
将具有值500,因为它从未被修改过,pmm->m
将具有值100.没有对象在序列点之间被修改两次。行为已定义。