通过赋值将一块内存复制到自身是否有效?
Is copying a chunk of memory into itself via assignment valid?
考虑这个例子:
T *fun(T *x) {
// do something with contents of (*x), do not change x itself
return x;
}
T var = ...;
var = *fun(&var); // <- is this valid?
突出显示的行最终获取一个指向变量的指针,取消引用它,并将指针指向的内存内容复制到同一位置
这是允许的吗?如果这使 UB 绊倒,为什么?这可以被认为是别名的例子吗?
这很好。该表达式具有更新 var
的副作用,但这发生在计算右侧 var
的值之后,因为必须在更新 var
之前计算右侧。
您所做的实际上与以下内容相同:
var = var;
如果func
修改*x
也可以,因为函数调用引入了序列点。
的第 6.5p2 节中提到了诸如此类的表达式
If a side effect on a scalar object is unsequenced relative
to either a different side effect on the same scalar object
or a value computation using the value of the same scalar
object, the behavior is undefined. If there are multiple
allowable orderings of the subexpressions of an expression, the
behavior is undefined if such an unsequenced side effect occurs
in any of the orderings.84)
84)) This paragraph renders undefined statement expressions such as
i = ++i + 1;
a[i++] = i;
while allowing
i=i+1;
a[i] = i
是的,var 的新值仅在表达式右侧完全计算后才赋值。
C 编译器通常需要考虑赋值运算符右侧的左值可能标识同一对象的可能性,而左侧的左值具有相同的类型并以类似的方式访问。它们通常不需要考虑表达式右侧使用的左值可能通过其他方式与左侧的左值交互的可能性。除其他事项外,给出如下内容:
void doSomething(long *p, long *q, long *r)
{
*r = *p + *q;
}
编译器可以加载 *p
的下半部分,添加 *q
的下半部分,将结果存储到 *r
的下半部分,然后加载上半部分*p
的上半部分 *q
加上任意进位,并将结果存储到 *r
的上半部分。这可能会发生故障,例如*r
的底部单词与 *q
的顶部单词占用相同的存储空间,即使代码在处理分配后永远不会尝试读取 *q
。
如果编译器没有理由认为赋值会修改源代码中使用的任何左值,它可能会执行额外的优化。例如,如果 _Bool
是一个字节的编译器给出如下内容:
int x;
void handleAssign(char *p)
{
x += p[0]*p[1] + p[2]*p[3];
}
它将有权处理它,就好像它是这样写的:
int temp;
void handleAssign(char *p)
{
x += p[0]*p[1]; // Using wrapping two's-complement math
x += p[2]*p[3]; // Using wrapping two's-complement math
}
这可能会提高平台上的效率,这些平台可以直接将乘法的结果添加到内存操作数,否则必须将其传输到另一个寄存器,将第二次乘法的结果添加到该寄存器,然后添加结果到 x。这种处理与基于类型的别名是分开的。
考虑这个例子:
T *fun(T *x) {
// do something with contents of (*x), do not change x itself
return x;
}
T var = ...;
var = *fun(&var); // <- is this valid?
突出显示的行最终获取一个指向变量的指针,取消引用它,并将指针指向的内存内容复制到同一位置
这是允许的吗?如果这使 UB 绊倒,为什么?这可以被认为是别名的例子吗?
这很好。该表达式具有更新 var
的副作用,但这发生在计算右侧 var
的值之后,因为必须在更新 var
之前计算右侧。
您所做的实际上与以下内容相同:
var = var;
如果func
修改*x
也可以,因为函数调用引入了序列点。
If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.84)
84)) This paragraph renders undefined statement expressions such as
i = ++i + 1; a[i++] = i;
while allowing
i=i+1; a[i] = i
是的,var 的新值仅在表达式右侧完全计算后才赋值。
C 编译器通常需要考虑赋值运算符右侧的左值可能标识同一对象的可能性,而左侧的左值具有相同的类型并以类似的方式访问。它们通常不需要考虑表达式右侧使用的左值可能通过其他方式与左侧的左值交互的可能性。除其他事项外,给出如下内容:
void doSomething(long *p, long *q, long *r)
{
*r = *p + *q;
}
编译器可以加载 *p
的下半部分,添加 *q
的下半部分,将结果存储到 *r
的下半部分,然后加载上半部分*p
的上半部分 *q
加上任意进位,并将结果存储到 *r
的上半部分。这可能会发生故障,例如*r
的底部单词与 *q
的顶部单词占用相同的存储空间,即使代码在处理分配后永远不会尝试读取 *q
。
如果编译器没有理由认为赋值会修改源代码中使用的任何左值,它可能会执行额外的优化。例如,如果 _Bool
是一个字节的编译器给出如下内容:
int x;
void handleAssign(char *p)
{
x += p[0]*p[1] + p[2]*p[3];
}
它将有权处理它,就好像它是这样写的:
int temp;
void handleAssign(char *p)
{
x += p[0]*p[1]; // Using wrapping two's-complement math
x += p[2]*p[3]; // Using wrapping two's-complement math
}
这可能会提高平台上的效率,这些平台可以直接将乘法的结果添加到内存操作数,否则必须将其传输到另一个寄存器,将第二次乘法的结果添加到该寄存器,然后添加结果到 x。这种处理与基于类型的别名是分开的。