我需要一种安全的方法来在任意POD类型之间进行别名,符合ISO-C++ 11,明确考虑n3242或更高版本的3.10/10和3.11.这里有很多关于严格别名的问题,其中大部分都是关于C而不是C++.我找到了一个使用联合的C的"解决方案",可能使用了这个部分
联合类型,包括其元素或非静态数据成员中的上述类型之一
从那我建立了这个.
#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';
}
Run Code Online (Sandbox Code Playgroud)
我的问题是,可以肯定的是,该计划是否符合标准?*
它没有给出任何警告,并且在使用MinGW/GCC 4.6.2进行编译时工作正常: …
据我所知,GCC支持C++中的所有C99功能.但是如何在C++代码中处理C99严格别名?
我知道在不相关的类型之间使用C转换进行转换不是严格别名安全的,并且可能生成错误的代码,但是C++呢?由于严格别名不是C++标准的一部分(这是正确的吗?),GCC必须指定语义本身.
我想,const_cast与static_cast相关类型之间的演员阵容,因此他们是安全的,而reinterpret_cast可以打破严格走样规则.
这是正确的理解吗?
鉴于代码:
struct s1 {unsigned short x;};
struct s2 {unsigned short x;};
union s1s2 { struct s1 v1; struct s2 v2; };
static int read_s1x(struct s1 *p) { return p->x; }
static void write_s2x(struct s2 *p, int v) { p->x=v;}
int test(union s1s2 *p1, union s1s2 *p2, union s1s2 *p3)
{
if (read_s1x(&p1->v1))
{
unsigned short temp;
temp = p3->v1.x;
p3->v2.x = temp;
write_s2x(&p2->v2,1234);
temp = p3->v2.x;
p3->v1.x = temp;
}
return read_s1x(&p1->v1);
}
int test2(int x)
{
union s1s2 q[2]; …Run Code Online (Sandbox Code Playgroud) 为可变大小的数组分配内存时,我经常这样做:
struct array {
long length;
int *mem;
};
struct array *alloc_array( long length)
{
struct array *arr = malloc( sizeof(struct array) + sizeof(int)*length);
arr->length = length;
arr->mem = (int *)(arr + 1); /* dubious pointer manipulation */
return arr;
}
Run Code Online (Sandbox Code Playgroud)
然后我使用这样的arrray:
int main()
{
struct array *arr = alloc_array( 10);
for( int i = 0; i < 10; i++)
arr->mem[i] = i;
/* do something more meaningful */
free( arr);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这工作和编译没有警告.然而,最近我读到了严格的别名.根据我的理解,上面的代码在严格别名方面是合法的,因为通过它访问int *的内存不是通过它访问的内存struct array …
该restrict关键字的行为在C99定义由6.7.3.1:
设D是普通标识符的声明,它提供了一种将对象P指定为类型T的限制限定指针的方法.
如果D出现在块内并且没有存储类extern,则让B表示该块.如果D出现在函数定义的参数声明列表中,则让B表示关联的块.否则,让B表示主块(或在独立环境中在程序启动时调用的任何函数块).
在下文中,指针表达式E被称为基于对象P if(在评估E之前执行B中的某个序列点)修改P以指向其先前指向的数组对象的副本将改变E.119的值)注意''based''仅为具有指针类型的表达式定义.
在每次执行B期间,让L为具有基于P的&L的任何左值.如果L用于访问它指定的对象X的值,并且X也被修改(通过任何方式),则以下要求适用:T不应该是const限定的.用于访问X值的每个其他左值也应具有基于P的地址.出于本子条款的目的,每次修改X的访问也应被视为修改P. 如果为P分配了指针表达式E的值,该指针表达式E基于与块B2相关联的另一个受限指针对象P2,则B2的执行应在执行B之前开始,或者B2的执行应在该执行之前结束.分配.如果不满足这些要求,则行为未定义.
就像其他人一样,我很难理解这个定义的所有复杂性.作为这个问题的答案,我希望看到第4段中每个要求违反要求的一些好例子.本文:
在"编译器可能假设......"方面做得很好.扩展该模式并将编译器可以做出的假设以及它们如何无法保持,每个示例都很棒.
(注意:虽然这个问题是关于"存储"的,但"加载"情况具有相同的问题并且是完全对称的.)
SSE内在函数提供_mm_storeu_pd具有以下签名的函数:
void _mm_storeu_pd (double *p, __m128d a);
Run Code Online (Sandbox Code Playgroud)
所以,如果我有两个双精度矢量,并且我想将它存储到两个双精度数组中,我可以使用这个内在函数.
但是,我的矢量不是两个双打; 它是两个64位整数,我想将它存储到两个64位整数的数组中.也就是说,我想要一个具有以下签名的函数:
void _mm_storeu_epi64 (int64_t *p, __m128i a);
Run Code Online (Sandbox Code Playgroud)
但内在函数没有提供这样的功能.他们最接近的是_mm_storeu_si128:
void _mm_storeu_si128 (__m128i *p, __m128i a);
Run Code Online (Sandbox Code Playgroud)
问题是这个函数需要一个指针__m128i,而我的数组是一个数组int64_t.通过错误类型的指针写入对象违反了严格的别名,并且肯定是未定义的行为.我担心我的编译器现在或将来会重新排序或以其他方式优化存储,从而以奇怪的方式破坏我的程序.
要清楚,我想要的是一个我可以这样调用的函数:
__m128i v = _mm_set_epi64x(2,1);
int64_t ra[2];
_mm_storeu_epi64(&ra[0], v); // does not exist, so I want to implement it
Run Code Online (Sandbox Code Playgroud)
以下是创建此类功能的六次尝试.
void _mm_storeu_epi64(int64_t *p, __m128i a) {
_mm_storeu_si128(reinterpret_cast<__m128i *>(p), a);
}
Run Code Online (Sandbox Code Playgroud)
这似乎有我担心的严格别名问题.
void _mm_storeu_epi64(int64_t *p, __m128i a) {
_mm_storeu_si128(static_cast<__m128i *>(static_cast<void *>(p)), a);
}
Run Code Online (Sandbox Code Playgroud)
以GCC优化命名的"严格别名"是编译器假设内存中的值不会通过类型的左值("声明的类型")访问,该值与写入的值的类型非常不同( "有效型").如果必须考虑写入指针float可以修改类型的全局变量,则该假设允许代码转换是不正确的int.
GCC和Clang都是从充满暗角的标准描述中提取出最多的含义,并且在实践中对生成代码的性能有偏见,假设指向a的int第一个成员的struct thing指针不会将指向第int一个成员的指针作为别名一个struct object:
struct thing { int a; };
struct object { int a; };
int e(struct thing *p, struct object *q) {
p->a = 1;
q->a = 2;
return p->a;
}
Run Code Online (Sandbox Code Playgroud)
GCC和Clang的推断该函数总是返回1,也就是说,p且q不能为同一个内存位置的别名:
e:
movl $1, (%rdi)
movl $1, %eax
movl $2, (%rsi)
ret
Run Code Online (Sandbox Code Playgroud)
只要有人同意这种优化的推理,就不足为奇了,p->t[3]并且q->t[2]在下面的代码片段中也假设是不相交的左值(或者更确切地说,如果它们别名,调用者会导致UB):
struct arr { int t[10]; };
int h(struct …Run Code Online (Sandbox Code Playgroud) 似乎有一些协议,char由于C++别名规则,你不能毫无疑问地将一个int(一个int*)指向一个数组.
从另一个问题 - 基于通用char []的存储和避免严格别名相关的UB - 它似乎允许(重新)通过新的放置使用存储.
alignas(int) char buf[sizeof(int)];
void f() {
// turn the memory into an int: (??) from the POV of the abstract machine!
::new (buf) int; // is this strictly required? (aside: it's obviously a no-op)
// access storage:
*((int*)buf) = 42; // for this discussion, just assume the cast itself yields the correct pointer value
}
Run Code Online (Sandbox Code Playgroud)
那么,上面是合法的C++ 并且是实际需要的新版本才能使其合法化吗?
c++ strict-aliasing placement-new primitive-types language-lawyer
谷歌严格别名的第一个结果之一就是这篇文章
http://dbp-consulting.com/tutorials/StrictAliasing.html
我注意到的一个有趣的事情是:http://goo.gl/lPtIa5
uint32_t swaphalves(uint32_t a) {
uint32_t acopy = a;
uint16_t* ptr = (uint16_t*)&acopy;
uint16_t tmp = ptr[0];
ptr[0] = ptr[1];
ptr[1] = tmp;
return acopy;
}
Run Code Online (Sandbox Code Playgroud)
被编译为
swaphalves(unsigned int):
mov eax, edi
ret
Run Code Online (Sandbox Code Playgroud)
由GCC 4.4.7.任何比这更新的编译器(文章中提到的4.4所以文章没有错)都没有实现该功能,因为它可以使用严格别名.这是什么原因?它实际上是GCC中的错误还是GCC决定放弃它,因为许多行代码是以产生UB的方式编写的,或者它只是一个持续多年的编译器回归...而Clang也没有优化它.
我有一个问题,了解使用GCC的工会可以做什么和不可以做什么.我阅读了有关它的问题(特别是这里和这里),但他们关注C++标准,我觉得C++标准和实践(常用的编译器)之间存在不匹配.
特别是,我最近在阅读有关编译标志-fstrict-aliasing的GCC在线文档中发现了令人困惑的信息.它说:
-fstrict走样
允许编译器采用适用于正在编译的语言的最严格的别名规则.对于C(和C++),这将根据表达式的类型激活优化.特别地,假设一种类型的对象永远不会与不同类型的对象驻留在相同的地址,除非类型几乎相同.例如,a
unsigned intcan可以是aint,但不是avoid*或adouble.字符类型可以别名为任何其他类型.特别注意这样的代码:Run Code Online (Sandbox Code Playgroud)union a_union { int i; double d; }; int f() { union a_union t; t.d = 3.0; return t.i; }从不同的工会成员阅读的做法比最近写的那个(称为"打字式")很常见.即使使用-fstrict-aliasing,只要通过union类型访问内存,就允许类型为punning.因此,上面的代码按预期工作.
这是我认为我从这个例子和我的疑虑中理解的:
1)别名仅适用于相似类型或char
1)的后果:别名 - 正如文字暗示的那样 - 是你有一个值和两个成员来访问它(即相同的字节);
怀疑:当它们具有相同的字节大小时,两种类型是相似的吗?如果没有,什么是类似的类型?
1)对于非相似类型(无论这意味着什么)的后果,别名不起作用;
2)类型双关语是指我们读的不同于我们写的成员; 它是常见的,只要通过union类型访问内存,它就可以正常工作;
怀疑:在类型相似的特定情况下别名是什么类型?
我感到困惑,因为它表示unsigned int和double不相似,所以别名不起作用; 然后在示例中它是int和double之间的别名,它清楚地表明它按预期工作,但称之为类型 - 惩罚:不是因为类型是或不相似,而是因为它是从一个不写的成员读取.但是从一个没有写的成员那里读取的是我所理解的混淆(正如这个词所暗示的那样).我迷路了.
问题: 有人可以澄清别名和类型惩罚之间的区别,这两种技术的用途是如何在GCC中发挥作用的?编译器标志有什么作用?
strict-aliasing ×10
c ×6
c++ ×6
gcc ×5
clang ×2
c++11 ×1
c99 ×1
intrinsics ×1
sse ×1
type-punning ×1
unions ×1