我使用以下代码来从文件中读取数据,作为更大程序的一部分.
double data_read(FILE *stream,int code) {
char data[8];
switch(code) {
case 0x08:
return (unsigned char)fgetc(stream);
case 0x09:
return (signed char)fgetc(stream);
case 0x0b:
data[1] = fgetc(stream);
data[0] = fgetc(stream);
return *(short*)data;
case 0x0c:
for(int i=3;i>=0;i--)
data[i] = fgetc(stream);
return *(int*)data;
case 0x0d:
for(int i=3;i>=0;i--)
data[i] = fgetc(stream);
return *(float*)data;
case 0x0e:
for(int i=7;i>=0;i--)
data[i] = fgetc(stream);
return *(double*)data;
}
die("data read failed");
return 1;
}
Run Code Online (Sandbox Code Playgroud)
现在我被告知使用-O2,我得到以下gcc警告:
warning: dereferencing type-punned pointer will break strict-aliasing rules
谷歌我找到了两个正交的答案:
VS
在查看Dear Imgui的代码时,我发现了以下代码(为了相关性进行了编辑):
struct ImVec2
{
float x, y;
float& operator[] (size_t idx) { return (&x)[idx]; }
};
Run Code Online (Sandbox Code Playgroud)
很明显,这在实践中是有效的,但是从 C++ 标准的角度来看,这段代码合法吗?如果没有,任何主要编译器(G++、MSVC、Clang)是否提供任何显式或隐式保证该代码将按预期工作?
请考虑以下两个片段:
#define ALIGN_BYTES 32
#define ASSUME_ALIGNED(x) x = __builtin_assume_aligned(x, ALIGN_BYTES)
void fn0(const float *restrict a0, const float *restrict a1,
float *restrict b, int n)
{
ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);
for (int i = 0; i < n; ++i)
b[i] = a0[i] + a1[i];
}
void fn1(const float *restrict *restrict a, float *restrict b, int n)
{
ASSUME_ALIGNED(a[0]); ASSUME_ALIGNED(a[1]); ASSUME_ALIGNED(b);
for (int i = 0; i < n; ++i)
b[i] = a[0][i] + a[1][i];
}
Run Code Online (Sandbox Code Playgroud)
当我编译函数时,gcc-4.7.2 -Ofast -march=native -std=c99 -ftree-vectorizer-verbose=5 -S …
你有任何恐怖故事要讲吗?GCC手册最近添加了一个关于-fstrict-aliasing的警告并通过联合转换指针:
[...]获取地址,强制生成指针并取消引用结果具有未定义的行为 [强调添加],即使转换使用了联合类型,例如:
union a_union {
int i;
double d;
};
int f() {
double d = 3.0;
return ((union a_union *)&d)->i;
}
Run Code Online (Sandbox Code Playgroud)
有没有人有一个例子来说明这种未定义的行为?
请注意,这个问题不是关于C99标准所说或不说的.它是关于gcc和其他现有编译器的实际功能.
我只是猜测,但一个潜在的问题可能在于设置d为3.0.因为d是永远不会直接读取的临时变量,并且永远不会通过"稍微兼容"的指针读取,所以编译器可能不会费心去设置它.然后f()将从堆栈中返回一些垃圾.
我的简单,天真,尝试失败了.例如:
#include <stdio.h>
union a_union {
int i;
double d;
};
int f1(void) {
union a_union t;
t.d = 3333333.0;
return t.i; // gcc manual: 'type-punning is allowed, provided...' (C90 6.3.2.3)
}
int f2(void) {
double d = 3333333.0;
return ((union a_union *)&d)->i; // gcc …Run Code Online (Sandbox Code Playgroud) 当我使用g ++编译这个示例代码时,我收到此警告:
警告:解除引用类型惩罚指针将破坏严格别名规则
[-Wstrict-aliasing]
代码:
#include <iostream>
int main()
{
alignas(int) char data[sizeof(int)];
int *myInt = new (data) int;
*myInt = 34;
std::cout << *reinterpret_cast<int*>(data);
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,不是data别名int,因此将其强制转换为int不会违反严格的别名规则?或者我在这里遗漏了什么?
编辑:奇怪,当我这样定义时data:
alignas(int) char* data = new char[sizeof(int)];
Run Code Online (Sandbox Code Playgroud)
编译器警告消失了.堆栈分配是否与严格别名产生差异?事实上它是一个char[]而不是一个char*意味着它实际上不能为任何类型别名吗?
正如先前建立的,形式的联合
union some_union {
type_a member_a;
type_b member_b;
...
};
Run Code Online (Sandbox Code Playgroud)
与Ñ成员包括Ñ在重叠存储+ 1对象:联合本身一个目的并且对于每个联盟成员一个对象.很明显,您可以按任何顺序自由地读取和写入任何工会成员,即使读取的工会成员不是最后写入的工会成员.永远不会违反严格别名规则,因为访问存储的左值具有正确的有效类型.
脚注95 进一步支持了这一点,脚注95解释了类型双关语是否是联盟的预期用途.
严格别名规则启用的优化的典型示例是此函数:
int strict_aliasing_example(int *i, float *f)
{
*i = 1;
*f = 1.0;
return (*i);
}
Run Code Online (Sandbox Code Playgroud)
编译器可能会优化到类似的东西
int strict_aliasing_example(int *i, float *f)
{
*i = 1;
*f = 1.0;
return (1);
}
Run Code Online (Sandbox Code Playgroud)
因为它可以安全地假设写入*f不会影响值*i.
但是,当我们将两个指针传递给同一个联盟的成员时会发生什么?考虑这个例子,假设一个典型的平台float是IEEE 754单精度浮点数,并且int是32位二进制补码整数:
int breaking_example(void)
{
union {
int i;
float f;
} fi;
return (strict_aliasing_example(&fi.i, &fi.f));
} …Run Code Online (Sandbox Code Playgroud) 我目前正在使用aligned_storage来实现类似于boost :: optional的'Optional'类型.要做到这一点,我有一个类成员,如下所示:
typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;
Run Code Online (Sandbox Code Playgroud)
我使用placement new来创建对象,但是我不存储返回任何地方的指针.相反,我在所有我的成员函数中访问对象的基础类型(显然通过检查确保对象通过也存储在我的Optional类型中的布尔标志有效):
T const* operator->() const {
return static_cast<T const*>(static_cast<void const*>(&t_));
}
Run Code Online (Sandbox Code Playgroud)
我的问题是这是否安全.我的理解是我对placement new的使用改变了对象的'动态类型',只要我继续使用该类型访问内存,我就没问题.但是我不清楚我是否必须保持从placement new返回的指针,或者我是否只允许在需要访问它时转换为底层类型.我已经阅读了C++ 11标准的第3.10节,但是我在标准方面还不够流利,无法确定.
如果可能的话,如果你能在答案中提到标准,我会感觉更好(这有助于我在晚上睡觉:P).
c++ strict-aliasing undefined-behavior language-lawyer type-punning
是否所有指针都是从指向结构类型的指针派生的问题都不容易回答.我发现这是一个重要问题,主要有以下两个原因.
A.缺少指向"任何"不完整或对象类型的指针,对方便的函数接口施加了限制,例如:
int allocate(ANY_TYPE **p,
size_t s);
int main(void)
{
int *p;
int r = allocate(&p, sizeof *p);
}
Run Code Online (Sandbox Code Playgroud)
[ 完整代码示例 ]
指向"任何"不完整或对象类型的现有指针明确描述为:
C99/ C11 §6.3.2.3 p1:
指向void的指针可以转换为指向任何不完整或对象类型的指针.[...]
从指向"任何"不完整或对象类型的现有指针派生的指针,指向void的指针,严格地是指向void的指针,并且不需要使用从指向"任何"不完整的指针派生的指针进行转换或对象类型.
B.程序员根据他们对特定实现的经验,使用基于不需要的假设的约定,有意识地或不知不觉地与指针的泛化相关,这种情况并不少见.假设,例如可转换,可表示为整数或共享公共属性:对象大小,表示或对齐.
根据C99 §6.2.5 p27/ C11 §6.2.5 p28:
[...]所有指向结构类型的指针应具有相同的表示和对齐要求.[...]
其次是C99 TC3 Footnote 39/ C11 Footnote 48:
相同的表示和对齐要求意味着可互换性作为函数的参数,函数的返回值和联合的成员.
虽然标准没有说:"指向结构类型的指针"并且选择了以下单词:"所有指向结构类型的指针",但它没有明确指定它是否适用于这种指针的递归推导.在标准中提到指针的特殊属性的其他情况下,它没有明确指定或提及递归指针派生,这意味着"类型派生"适用,或者它没有 - 但它没有明确提到.
虽然在引用类型时使用"所有指针"的措辞只使用两次,(对于结构和联合类型),而不是更明确的措辞:在整个标准中使用的"指针",我们不能总结它是否适用于这种指针的递归推导.
我最初的问题是,在一个项目中,我有几个共享一生的对象(即,一旦我释放其中一个,我将释放它们全部),然后我想分配一个单独的内存块.我有三个不同的对象类型的数组,struct foo,void *,和char.起初我想要malloc()一个像这样的块:
// +---------------+---------+-----------+---------+---------+
// | struct foo[n] | padding | void *[m] | padding | char[o] |
// +---------------+---------+-----------+---------+---------+
Run Code Online (Sandbox Code Playgroud)
但那么......如果不调用未定义的行为,我怎么能做到这一点呢?即,尊重类型别名规则,aligment ...如何正确计算内存块大小,声明内存块(具有有效类型),以及如何正确地获取指向其中所有三个部分的指针?
(我明白我可以使用malloc()3个块,这会导致3个块,free()但我想知道如何使用单个块来执行它,同时仍然表现良好.)
我想将我的问题扩展到一个更普遍的问题:应该采取什么预防措施来为任意大小和对齐的对象实现内存池,同时保持程序的良好运行?(假设可以在不调用未定义行为的情况下实现它.)
注意:此问题已重命名并缩小,以使其更具针对性和可读性.大多数评论都涉及旧文本.
根据标准,不同类型的对象可能不共享相同的存储位置.所以这不合法:
std::array<short, 4> shorts;
int* i = reinterpret_cast<int*>(shorts.data()); // Not OK
Run Code Online (Sandbox Code Playgroud)
但是,该标准允许此规则的例外:可以通过指向char或的指针访问任何对象unsigned char:
int i = 0;
char * c = reinterpret_cast<char*>(&i); // OK
Run Code Online (Sandbox Code Playgroud)
但是,我不清楚这是否也允许反过来.例如:
char * c = read_socket(...);
unsigned * u = reinterpret_cast<unsigned*>(c); // huh?
Run Code Online (Sandbox Code Playgroud) strict-aliasing ×10
c ×6
c++ ×4
gcc ×3
pointers ×3
optimization ×2
struct ×2
type-punning ×2
c11 ×1
c99 ×1
unions ×1