我喜欢代码高尔夫。根据{{3}},我想知道我的任何“高尔夫”代码是否会引发竞争条件。
假设我们有两个函数,它们都返回一个布尔值,我们正在初始化一个名为result
的布尔变量:
result = foo() || bar();
在理想的世界中,我们有两种情景:
foo
返回true
。 不致电bar
。 result
等于true
。 [短路情景] foo
返回false
。 致电bar()
。 result
等于bar
返回的内容。 我的问题:是否会有违反短路评估的时间,尽管bar
返回foo
,甚至会调用true
,甚至更糟糕的是,在调用bar
之前调用foo
,可能是因为多线程?如果是这样,你能提供一段会触发这种行为的代码吗?
你的答案可能是关于这种语法有效的任何语言,尽管我认为某些语言对这类事物的要求会比其他语言更严格。
答案 0 :(得分:11)
当一系列操作的结果取决于执行它们的顺序时,就会出现竞争条件。
C ++中的&&
和||
运算符保证从左到右的评估,如果第一个运算符分别为false / true,则不评估第二个运算符。由于操作序列得到保证,因此foo
的操作与bar
的操作之间不存在竞争条件。但是,每个中的操作之间仍然存在竞争条件。
违反上述保证的代码不是C ++代码,同样符合C ++编译器的代码也绝不会发出违反这些保证的代码。
答案 1 :(得分:3)
您指的情况不是竞争条件。 让我们再回到你的问题:
是否会有违反短路评估的时间, 尽管foo返回true,但仍然会调用bar,或者更糟糕的是,bar就是 调用foo之前调用,可能是因为多线程?
我想正确的问题是:
是否有时间 bar 被调用 AFTER foo 使用 false 结果调用,但是当它被称为 foo 时将返回 true ,如果再次被称为 强>
好的,让我们做一些潜在的race condition
州的代码。
#define BUFFER_SIZE 0x1000
char globalBuffer[BUFFER_SIZE];
bool foo() { // have user an access to the path_to_file file?
return access(path_to_file, 0666) != 0; // path_to_file declared somewhere
}
bool bar() {
FILE *file = fopen(path_to_file, "r");
if (file == NULL) return false;
char *ptr = gfets(globalBuffer, BUFFER_SIZE, file);
if (ptr == NULL) return false;
return true;
}
...
result = foo() || bar(); // if foo is false, then user have an access
printf("%s", globalBuffer);
让我们假设我们可以控制path_to_file。
如果我们像这样进行无限循环
,将会出现竞争条件#!/bin/bash
while :
do
ln -s /path/to/good/access/file /path/to/file
rm -f /path/to/file
ln -s /etc/shadow /path/to/file
done
经过一些尝试,如果您的应用程序有suid位 - 我将读取/ etc / shadow
的内容但让我们回到你的问题:
不,没办法。如果foo
将返回true,则不会调用bar
。即使是其他线程,如果你有多线程。每个线程都有自己的寄存器,自己的堆栈。因此,如果在同一个表达式中有两个函数调用,您应该相信c ++标准。但这并不意味着您的代码是安全的。并且竞争条件是100个可能的问题之一。
答案 2 :(得分:1)
请注意,C ++支持运算符重载,但该短路仅是内置运算符(pre c ++ 17)的一个特性,而不是用户提供的重载。
即。在所有当前版本的C ++中,假设发生短路是不安全的,除非你知道这两种类型并且知道没有自由函数运算符||是阴影。
请参阅p0145了解这在c ++ 17中的变化情况。
(粗体是添加,斜体删除)
更改第5/2段如下:[注意:操作员可能会超载, 也就是说,当应用于类类型的表达式时给定含义 (第9条)或查点类型(7.2)。重载运算符的使用是 转换为函数调用,如13.5中所述。重载 运营商遵守指定的语法和评估顺序的规则 第5条,但操作数类型*,* 和值类别的要求, 和评估顺序被函数调用的规则所取代。 运算符之间的关系,例如++ a表示+ = 1,则不是 保证重载运算符(13.5),并且不保证 bool类型的操作数。 - 后注]