一般地,对于int num,num++(或++num),作为读-修改-写操作中,是不是原子.但我经常看到编译器,例如GCC,为它生成以下代码(在这里尝试):
由于第5行对应于num++一条指令,我们可以得出结论,在这种情况下num++ 是原子的吗?
如果是这样,是否意味着如此生成num++可以在并发(多线程)场景中使用而没有任何数据争用的危险(例如,我们不需要制作它,std::atomic<int>并强加相关成本,因为它是无论如何原子)?
UPDATE
请注意,这个问题不是增量是否是原子的(它不是,而且是问题的开头行).它是否可以在特定场景中,即在某些情况下是否可以利用单指令性质来避免lock前缀的开销.而且,作为公认的答案约单处理器的机器,还有部分提到这个答案,在其评论和其他人谈话解释,它可以(尽管不是C或C++).
假设我有两个.cpp文件file1.cpp和file2.cpp:
// file1.cpp
#include <iostream>
inline void foo()
{
std::cout << "f1\n";
}
void f1()
{
foo();
}
Run Code Online (Sandbox Code Playgroud)
和
// file2.cpp
#include <iostream>
inline void foo()
{
std::cout << "f2\n";
}
void f2()
{
foo();
}
Run Code Online (Sandbox Code Playgroud)
在main.cpp我已经向前宣布f1()和f2():
void f1();
void f2();
int main()
{
f1();
f2();
}
Run Code Online (Sandbox Code Playgroud)
结果(不依赖于构建,调试/发布版本的结果相同):
f1
f1
Run Code Online (Sandbox Code Playgroud)
哇:编译器以某种方式仅选择定义file1.cpp并在其中使用它f2().这种行为的确切解释是什么?
注意,改变inline到static是这个问题的解决方案.将内联定义放在未命名的命名空间中也可以解决问题并且程序打印:
f1
f2
Run Code Online (Sandbox Code Playgroud) 看起来我们正在为C++获得全新的"面试问题"(我希望不是,实际上).
众所周知,在C++ 17之前,它是未定义的行为,但它是否会从C++ 17开始明确定义?
由于目前似乎没有一个编译器实现了这个C++ 17修改,根据表达式评估规则,任何人都可以解释x在下面的代码中的值是什么?
int i = 0;
int x = i++ + i++;
Run Code Online (Sandbox Code Playgroud)
阿利斯代尔梅雷迪思提到这个例子在这里他CppCon 2016的谈话,但它并不完全清楚,我的最终值什么x会(虽然它似乎什么他要说的是,这将是至少1).
显然,i在这种情况下,本身将在表达式结尾处为2.
c++ operator-precedence undefined-behavior language-lawyer c++17
例如:
constexpr int g() { return 30; }
constexpr int f()
{
// Can we omit const?
const int x = g();
const int y = 10;
return x + y;
}
Run Code Online (Sandbox Code Playgroud)
有没有必要在constexpr函数中声明局部变量const?
局部变量的constexpr函数不等于没有const局部变量的函数吗?const
换句话说,constexpr函数是否强加(暗示)const它的局部变量?
亚历山大·斯捷潘诺夫在A9的一篇精彩讲座(强烈推荐,顺便说一句)中指出,关联属性为我们提供了可并行性 - 这些日子是编译器,CPU和程序员自己可以利用的非常有用和重要的特性:
// expressions in parentheses can be done in parallel
// because matrix multiplication is associative
Matrix X = (A * B) * (C * D);
Run Code Online (Sandbox Code Playgroud)
但是,交换性财产给我们带来了什么?重新排序?乱序执行?
math parallel-processing cpu cpu-architecture compiler-optimization
显然,不允许在ref-qualifiers上重载 - 如果删除&或者&&(只是标记,而不是它们的函数),这段代码将无法编译:
#include <iostream>
struct S {
void f() & { std::cout << "Lvalue" << std::endl; }
void f() && { std::cout << "Rvalue" << std::endl; }
};
int main()
{
S s;
s.f(); // prints "Lvalue"
S().f(); // prints "Rvalue"
}
Run Code Online (Sandbox Code Playgroud)
换句话说,如果您有两个具有相同名称和类型的函数,则必须定义两者,如果您定义其中任何一个.我认为这是故意的,但是原因是什么?例如,为什么不允许调用&&rvalues 的版本(如果已定义),以及f()以下变体中的其他所有内容的"primary" (反之亦然 - 尽管这会让人感到困惑):
struct S {
void f() { std::cout << "Lvalue" << std::endl; }
void f() && { std::cout << "Rvalue" …Run Code Online (Sandbox Code Playgroud) 忽视这种做法的有用性.(当然,欢迎现实生活中的例子.)
例如,以下程序输出正确的值a:
#include <iostream>
using namespace std;
int main()
{
int a = 11111;
int i = 30;
int* pi = new (&i) int();
cout << a << " " << endl;
}
Run Code Online (Sandbox Code Playgroud)
但是,新的分配是不是应该创建一些与i(正确的后续解除分配)相邻的簿记信息,在这种情况下应该破坏堆栈i?
例如:
const int* pc = new const int(3); // note the const
int* p = const_cast<int*>(pc);
*p = 4; // undefined behavior?
Run Code Online (Sandbox Code Playgroud)
特别是,编译器能否优化掉堆分配的堆*pc?
如果没有,修改,试图*pc通过p仍然构成未定义的行为-如果是这样,为什么呢?
c++ heap-memory const-cast compiler-optimization undefined-behavior
高速缓存行通常为64 字节,也存在其他大小。
我非常简单的问题是:这个数字背后是否有任何理论,或者它只是背后的工程师无疑所做的大量测试和测量的结果?
不管怎样,我想知道这些是什么(理论,如果有的话,以及决定背后的各种测试)。
换句话说,L1,L2,L3等缓存是否总是反映其CPU的字节顺序?
或者总是将数据存储在某些特定字节序的缓存中更有意义吗?
是否有一般设计决定?
例如:
struct B { int b_; };
struct D : B
{
~D()
{ // D object's lifetime ends here
d_ = 0; // (1) undefined behavior?
b_ = 0; // (2) undefined behavior also?
}
int d_;
};
Run Code Online (Sandbox Code Playgroud)
在C++标准定义,对于类型的对象D,其寿命结束析构函数时~D()呼叫开始.
我们可以解释这意味着修改析构函数内的对象,如(1)所示,会导致未定义的行为吗?
如果是这样,如果我们修改基类子对象D,如同在(2)中那样适用吗?
这是有问题的代码示例:
struct A {
A() = delete;
};
int main()
{
// A a(); // compiles, since it's a function declaration (most vexing parse)
// A a; // does not compile, just as expected
A a{}; // compiles, why? The default constructor is deleted.
}
Run Code Online (Sandbox Code Playgroud)
在这里尝试使用任何可用的编译器.我尝试了几个,没有找到一个编译错误.
这就是我的意思:i++ + i++未定义,对数组的越界写入也是如此.
超出范围的数组写入的不确定性是可以理解的:它可能被利用来运行任意代码,这是您可以获得的未定义代码.让我们调用这个运行时未定义的行为.
对于i++ + i++,然而,故事似乎是不同的.假设编译器生成了一些东西.它究竟是什么未定义的.非常不确定.事实上,它是如此不确定,我们曾经听过猫可能怀孕(虽然最近 - 从CppCon 2016,我认为 - 人们开始意识到,未定义的行为毕竟不能让猫怀孕).
但是,一旦我们打开盒子,看看编译器生成了什么,并且它不是可利用的代码(注入,数据竞争等等 - 例如,编译器选择i++ + i++完全抛弃),是不是它将完全被执行 -是不是从那一点上完美定义了?
换句话说,最后一种情况是我们可以称之为编译时未定义的行为.用猫来说,它类似于薛定谔的猫,在你打开盒子之前它的状态是未知的(参见生成的装配),此时你会看到要执行的实际现实.(我想知道未定义的行为是否会使有毒的死猫怀孕.)
当然,未定义的行为是适用于该标准的法律术语.问题是关于现实中发生的"行为".
c++ ×10
c++11 ×3
cpu ×3
cpu-cache ×2
heap-memory ×2
assembly ×1
atomic ×1
c ×1
c++14 ×1
c++17 ×1
const ×1
const-cast ×1
constexpr ×1
destructor ×1
endianness ×1
inline ×1
lifetime ×1
math ×1
new-operator ×1
overloading ×1
rvalue ×1
stack ×1