如果 func() 修改全局变量 [duplicate],int sum = func(1) + func(2) 是否会导致未定义的行为

Does int sum = func(1) + func(2) cause undefined behavior if func() modifies a global variable [duplicate]

提问人:D.J. Elkind 提问时间:11/1/2023 最后编辑:Peter CordesD.J. Elkind 更新时间:11/1/2023 访问量:155

问:

受这篇 SO 帖子的启发,我想知道下面的代码片段是否会导致 UB 同时发生,并且可以同时修改并按未指定的顺序进行修改:add_func()mul_func()counter

int counter = 0;

int mul_func(int x) {
  counter *= x;
  x = counter;
  return x;
}

int add_func(int x) {
  counter += x;
  x = counter;
  return x;
}

int main(void) {
  int sum = add_func(3) + mul_func(2);
}

如果是这样,如果我向它们添加互斥锁是否有帮助?

int counter = 0;

pthread_mutex_d mtx;

int mul_func(int x) {
  pthread_mutex_lock(&mtx);
  counter *= x;
  x = counter;
  pthread_mutex_unlock(&mtx);
  return x;
}

int add_func(int x) {
  pthread_mutex_lock(&mtx);
  counter += x;
  x = counter;
  pthread_mutex_unlock(&mtx);
  return x;
}

int main(void) {
  pthread_mutex_init(&mtx);
  int sum = add_func(3) + mul_func(2);
  pthread_mutex_destroy(&mtx);
}

虽然结果可能是不确定的(就像许多多线程过程一样),但即使没有数据争用,“未排序”的性质是否仍然会导致 UB?

c 未定义行为 序列点

评论

1赞 Peter Cordes 11/1/2023
基本上是 func() + func() 未定义行为的副本? - 函数调用使它不确定地排序,而不是未排序。
1赞 Weijun Zhou 11/1/2023
@PeterCordes这个问题是关于C++的。C 有自己的规则。
2赞 Some programmer dude 11/1/2023
虽然函数调用的顺序可能是不确定的,或者留给实现(没有详细信息),但行为本身是明确定义的。尽管结果是不确定的,但没有真正的UB(我认为)。如果顺序很重要,则将表达式拆分为多个子表达式,将中间结果存储在临时变量中。
3赞 Sergey Kalinichenko 11/1/2023
输入函数和从函数返回都是序列点,因此此代码没有任何“未序列化”的内容。它有效并不意味着它是一个好的模式,但我相信你已经知道了:-)
4赞 Eric Postpischil 11/1/2023
@Someprogrammerdude:你认为这是如何定义的?它修改两次而不进行排序。甚至或会有未定义的行为,更不用说了。++i + ++iii + ++i++i + i++i + ++i

答:

2赞 Christian Stieber 11/1/2023 #1

这确实取决于编译器来决定它想要调用函数的顺序。它会在添加之前调用两者,但这就是你所知道的。

你的互斥锁不做任何事情;这些是用于同步线程的,而不是强制编译按照您想要的顺序计算内容。你的代码不是并行地做事,它只是以未指定的顺序做事。

强制执行排序的正确方法是

const int left=add_func(3);
const int right=mul_func(2);
int sum=left+right;

这样,编译器别无选择,只能按照您提供的顺序调用函数。

更新:因此,正如其中一条评论中所建议的那样,让我稍微强调一下措辞。根据语言定义,您的代码不是“未定义的行为”;实际上,您得到了两种可能的结果之一,这些结果定义得非常明确:如果编译器决定先调用add_func,然后再调用mul_func则会出现一个结果,而如果编译器更喜欢另一个调用顺序,则会出现另一个结果。

调用这两个函数的顺序是“未指定”的,但它们总是一个接一个地调用,从不并行调用。编译器或 CPU 可能添加的任何引入某种程度并行性的优化都不会改变这一点;您将始终获得两种可能的结果之一。

从另一个角度想一想:如果编译器(或CPU,或上帝)会添加一些随机并行性,这需要你采取某些预防措施,那么对程序行为的这种意外变化会破坏每个软件。 优化总是使用术语,比如不改变“可观察的行为”,这是有原因的。

是的,C++允许某些优化来改变“可观察的行为”,但这在语言规范中明确规定,而不是编译器(或CPU)一时兴起做的事情。

评论

0赞 D.J. Elkind 11/1/2023
我的问题不是关于强制排序,你可以参考这个答案,我的看法是,这意味着 CPU 可以有一些隐式并行性,这就是被标记为未定义的根本原因。int a = ++i + ++i
0赞 Christian Stieber 11/1/2023
不,这不是根本原因。仍然需要并行执行操作的 CPU 不改变程序的行为。编译器也是如此——是的,它可以使事情并行,但它的工作是确保这两个函数不会发生冲突。您的示例是未定义的,因为参数计算的顺序也未指定。
0赞 D.J. Elkind 11/1/2023
在“未指定的论证评估顺序”中,哪一个被称为论证?你是说和争论吗?或?32
3赞 Useless 11/1/2023
表达式 和 的结果是运算符的参数。仅仅因为您不知道哪个先被评估,并不意味着它们可以并行评估。它们是按顺序发生的,只是标准没有指定使用两个可能的订单中的哪一个。add_func(3)mul_func(2)+
3赞 Peter Cordes 11/1/2023
如果它明确地说没有 UB,函数体是不确定的,那么这个答案会好得多。OP 担心它们像以前那样没有排序i++ + ++i