感谢C++ 11,我们收到了std::functionfunctor包装器系列.不幸的是,我一直只听到关于这些新增内容的不好的事情.最受欢迎的是它们非常慢.我对它进行了测试,与模板相比,它们真的很糟糕.
#include <iostream>
#include <functional>
#include <string>
#include <chrono>
template <typename F>
float calc1(F f) { return -1.0f * f(3.3f) + 666.0f; }
float calc2(std::function<float(float)> f) { return -1.0f * f(3.3f) + 666.0f; }
int main() {
using namespace std::chrono;
const auto tp1 = system_clock::now();
for (int i = 0; i < 1e8; ++i) {
calc1([](float arg){ return arg * 0.5f; });
}
const auto tp2 = high_resolution_clock::now();
const auto d = duration_cast<milliseconds>(tp2 - tp1);
std::cout << …Run Code Online (Sandbox Code Playgroud) 我需要一个函数(就像来自WinAPI的SecureZeroMemory)总是将内存归零并且不会被优化掉,即使编译器认为在此之后永远不会再访问内存.似乎是挥发性的完美候选者.但是我实际上遇到了一些与GCC合作的问题.这是一个示例函数:
void volatileZeroMemory(volatile void* ptr, unsigned long long size)
{
volatile unsigned char* bytePtr = (volatile unsigned char*)ptr;
while (size--)
{
*bytePtr++ = 0;
}
}
Run Code Online (Sandbox Code Playgroud)
很简单.但是,如果你调用它,GCC实际生成的代码会因编译器版本和你实际尝试为零的字节数而有很大不同.https://godbolt.org/g/cMaQm2
我测试过的任何其他编译器(clang,icc,vc)都会生成一个人们期望的存储,包括任何编译器版本和任何数组大小.所以在这一点上我想知道,这是一个(非常古老而严重的?)GCC编译器错误,或者是标准中volatile的定义,它不确定这实际上是符合行为的,这使得编写便携式基本上是不可能的" SecureZeroMemory"功能?
编辑:一些有趣的观察.
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <atomic>
void callMeMaybe(char* buf);
void volatileZeroMemory(volatile void* ptr, std::size_t size)
{
for (auto bytePtr = static_cast<volatile std::uint8_t*>(ptr); size-- > 0; )
{
*bytePtr++ = 0;
}
//std::atomic_thread_fence(std::memory_order_release);
}
std::size_t foo()
{
char …Run Code Online (Sandbox Code Playgroud) 这将是一个漫长的过程,关于它的上下文并提供尽可能多的信息,我必须通过各种链接和引用来蜿蜒 - 这通常是我们进入C/C++标准兔子洞后的唯一方法.如果您对此帖有更好的引用或任何其他改进,请告诉我.但是要事先总结,你可以责怪@ zwol发布这个;-)并且目的是从两个命题中找到真相:
volatile *或volatile &必须引用最初声明的对象volatile才能具有volatile语义?volatile通过volatile指针/引用访问非限定对象足够/应该使所述访问行为就像声明对象一样volatile?无论哪种方式,如果(看起来)措辞与意图相比有些含糊不清 - 我们能否在标准本身中明确说明?
这些相互排斥的解释中的第一个更常见,而且并非完全没有依据.但是,我希望表明对第二个问题有很大的"合理怀疑" - 尤其是当我们回到基本原理和WG论文中的一些先前段落时.
volatile昨天流行的问题是"挥发性"这个波动的定义,还是GCC有一些标准的合规性问题?通过假设一个volatile引用会赋予volatile非volatile指示物行为- 但发现它没有,或者在不同程度上以不可预测的方式发生行为而产生.
接受的答案最初得出结论,只有声明的指称类型才重要.这个和大多数评论似乎都同意我们所熟知的等效原则在起作用const:volatile如果引用与引用对象具有相同的cv -qualification,则行为将仅(或根本定义):
该段落中的关键词是对象.
volatile sig_atomic_t flag;是一个易变的对象.*(volatile char *)foo仅仅是通过挥发性合格左值的访问,标准不要求具有任何特殊效果.- zwol
这种解释似乎得到了广泛的解释,正如对这个相似但希望不重复的问题的回答所示:指向易失性指向非易失性对象的行为要求但即使在那里也存在不确定性:答案是'不',然后说'也许'!无论如何......让我们检查一下标准,看看'不是'的基础.
C11,N1548,§6.7.3:虽然很明显它是UB来访问一个定义 的对象volatile或const …
编译器无法消除或重新排序对volatile限定变量的读/写.
但是,存在其他变量的情况又volatile如何呢?
volatile int a;
volatile int b;
a = 1;
b = 2;
a = 3;
b = 4;
Run Code Online (Sandbox Code Playgroud)
编译器可以重新排序第一次和第二次,第三次和第四次分配吗?
volatile int a;
int b, c;
b = 1;
a = 1;
c = b;
a = 3;
Run Code Online (Sandbox Code Playgroud)
同样的问题,编译器可以重新排序第一个和第二个,或第三个和第四个任务吗?
写入 volatile 变量是C++中的某种副作用,通常不能在 as-if 规则下优化。在实践中,这通常意味着在检查程序集时,您会看到抽象机器1为每个易失性存储提供一个存储。
但是,我不清楚是否必须在以下情况下执行存储,其中底层对象不是 volatile,但存储是通过指向 volatile 的指针完成的:
void vtest() {
int buf[1];
volatile int * vptr = buf;
*vptr = 0;
*vptr = 1;
*vptr = 2;
}
Run Code Online (Sandbox Code Playgroud)
在这里,gcc 实际上优化了所有的存储。叮当没有。奇怪的是,行为取决于缓冲区大小:buf[3]gcc 会发出存储,但buf[4]不会等等。
gcc在这里的行为合法吗?
[1] 有一些小的变化,例如,一些编译器将在 x86 上使用单个读-修改-写指令来实现诸如v++wherev是 volatile 之类的东西)。