- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
我需要一种在任意 POD 类型之间进行别名的安全方法,明确考虑到 n3242 或更高版本的 3.10/10 和 3.11,符合 ISO-C++11。这里有很多关于严格别名的问题,其中大部分是关于 C 而不是 C++。我找到了一个使用 union 的 C 的“解决方案”,可能使用本节
union type that includes one of the aforementioned types among its elements or nonstatic data members
我由此构建了这个。
#include <iostream>
template <typename T, typename U>
T& access_as(U* p)
{
union dummy_union
{
U dummy;
T destination;
};
dummy_union* u = (dummy_union*)p;
return u->destination;
}
struct test
{
short s;
int i;
};
int main()
{
int buf[2];
static_assert(sizeof(buf) >= sizeof(double), "");
static_assert(sizeof(buf) >= sizeof(test), "");
access_as<double>(buf) = 42.1337;
std::cout << access_as<double>(buf) << '\n';
access_as<test>(buf).s = 42;
access_as<test>(buf).i = 1234;
std::cout << access_as<test>(buf).s << '\n';
std::cout << access_as<test>(buf).i << '\n';
}
我的问题是,为了确定,这个程序按照标准合法吗?*
在使用 MinGW/GCC 4.6.2 进行编译时,它不会发出任何警告并且工作正常:
g++ -std=c++0x -Wall -Wextra -O3 -fstrict-aliasing -o alias.exe alias.cpp
* 编辑:如果不是,怎么可能修改为合法?
最佳答案
这永远不会是合法的,无论你用奇怪的 Actor 和 union 等等进行什么样的扭曲。
基本事实是:两个不同类型的对象可能永远不会在内存中出现别名,除了一些特殊异常(exception)(请参阅下文)。
考虑以下代码:
void sum(double& out, float* in, int count) {
for(int i = 0; i < count; ++i) {
out += *in++;
}
}
让我们将其分解为本地寄存器变量以更紧密地模拟实际执行:
void sum(double& out, float* in, int count) {
for(int i = 0; i < count; ++i) {
register double out_val = out; // (1)
register double in_val = *in; // (2)
register double tmp = out_val + in_val;
out = tmp; // (3)
in++;
}
}
假设 (1)、(2) 和 (3) 分别表示内存读取、读取和写入,在如此紧密的内部循环中,这可能是非常昂贵的操作。此循环的合理优化如下:
void sum(double& out, float* in, int count) {
register double tmp = out; // (1)
for(int i = 0; i < count; ++i) {
register double in_val = *in; // (2)
tmp = tmp + in_val;
in++;
}
out = tmp; // (3)
}
这种优化将所需的内存读取次数减少了一半,将内存写入次数减少到 1。这会对代码的性能产生巨大影响,对于所有优化 C 和 C++ 编译器来说都是非常重要的优化。
现在,假设我们没有严格的别名。假设写入任何类型的对象都会影响任何其他对象。假设写入 double 值会影响某处 float 的值。这使得上述优化变得可疑,因为程序员实际上可能打算将 out 和 in 设置为别名,从而使 sum 函数的结果更加复杂并受到过程的影响。听起来很愚蠢?即便如此,编译器也无法区分“愚蠢”和“聪明”的代码。编译器只能区分格式正确和格式错误的代码。如果我们允许自由别名,那么编译器必须在其优化中保持保守,并且必须在循环的每次迭代中执行额外的存储 (3)。
希望您现在能明白为什么没有这样的 union 或施法技巧可能是合法的。你不能通过花招来规避这样的基本概念。
C 和 C++ 标准为使用 char
和任何“相关类型”(其中包括派生类型和基类型以及成员)为任何类型设置别名,因为能够使用独立的类(class)成员的地址是如此重要。您可以在 this answer. 中找到这些规定的详尽列表。
此外,GCC 为从与上次写入内容不同的 union 成员读取数据做出了特殊规定。请注意,这种通过 union 进行的转换实际上不允许您违反别名。任何时候都只允许一个 union 的成员处于事件状态,因此例如,即使使用 GCC,以下行为也是未定义的:
union {
double d;
float f[2];
};
f[0] = 3.0f;
f[1] = 5.0f;
sum(d, f, 2); // UB: attempt to treat two members of
// a union as simultaneously active
将一个对象的位重新解释为其他类型对象的位的唯一标准方法是使用 memcpy
的等效项。这利用了对 char
对象的别名的特殊规定,实际上允许您在字节级别读取和修改底层 object 表示。例如,以下是合法的,并且不违反严格的别名规则:
int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
memcpy(a, &d, sizeof(d));
这在语义上等价于以下代码:
int a[2];
double d;
static_assert(sizeof(a) == sizeof(d));
for(size_t i = 0; i < sizeof(a); ++i)
((char*)a)[i] = ((char*)&d)[i];
GCC 规定从不活动的 union 成员读取数据,隐含地使其处于事件状态。来自 GCC documentation:
The practice of reading from a different union member than the one most recently written to (called “type-punning”) is common. Even with -fstrict-aliasing, type-punning is allowed, provided the memory is accessed through the union type. So, the code above will work as expected. See Structures unions enumerations and bit-fields implementation. However, this code might not:
int f() {
union a_union t;
int* ip;
t.d = 3.0;
ip = &t.i;
return *ip;
}
Similarly, access by taking the address, casting the resulting pointer and dereferencing the result has undefined behavior, even if the cast uses a union type, e.g.:
int f() {
double d = 3.0;
return ((union a_union *) &d)->i;
}
(注意:我在这里凭内存进行,因为我现在无法访问标准)。一旦将一个对象放置到存储缓冲区中,底层存储对象的生命周期就会隐式结束。这类似于您给 union 成员写信时发生的情况:
union {
int i;
float f;
} u;
// No member of u is active. Neither i nor f refer to an lvalue of any type.
u.i = 5;
// The member u.i is now active, and there exists an lvalue (object)
// of type int with the value 5. No float object exists.
u.f = 5.0f;
// The member u.i is no longer active,
// as its lifetime has ended with the assignment.
// The member u.f is now active, and there exists an lvalue (object)
// of type float with the value 5.0f. No int object exists.
现在,让我们看一下与placement-new类似的东西:
#define MAX_(x, y) ((x) > (y) ? (x) : (y))
// new returns suitably aligned memory
char* buffer = new char[MAX_(sizeof(int), sizeof(float))];
// Currently, only char objects exist in the buffer.
new (buffer) int(5);
// An object of type int has been constructed in the memory pointed to by buffer,
// implicitly ending the lifetime of the underlying storage objects.
new (buffer) float(5.0f);
// An object of type int has been constructed in the memory pointed to by buffer,
// implicitly ending the lifetime of the int object that previously occupied the same memory.
出于显而易见的原因,这种隐式的生命周期结束只会发生在具有微不足道的构造函数和析构函数的类型中。
关于c++ - 严格的混叠和对齐,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9964418/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!