标签: strict-aliasing

严格的混叠和对齐

我需要一种安全的方法来在任意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进行编译时工作正常: …

c++ strict-aliasing unions type-punning c++11

19
推荐指数
3
解决办法
6098
查看次数

C++中的C99严格别名规则(GCC)

据我所知,GCC支持C++中的所有C99功能.但是如何在C++代码中处理C99严格别名?

我知道在不相关的类型之间使用C转换进行转换不是严格别名安全的,并且可能生成错误的代码,但是C++呢?由于严格别名不是C++标准的一部分(这是正确的吗?),GCC必须指定语义本身.

我想,const_caststatic_cast相关类型之间的演员阵容,因此他们是安全的,而reinterpret_cast可以打破严格走样规则.

这是正确的理解吗?

c c++ gcc strict-aliasing

18
推荐指数
1
解决办法
7575
查看次数

这种工会的使用是否严格符合?

鉴于代码:

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)

c gcc clang strict-aliasing

18
推荐指数
1
解决办法
608
查看次数

如何在一个malloc调用中为数组和结构分配内存而不破坏严格的别名?

为可变大小的数组分配内存时,我经常这样做:

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 …

c memory-management strict-aliasing

18
推荐指数
2
解决办法
716
查看次数

通过示例了解限制限定符

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段中每个要求违反要求的一些好例子.本文:

http://web.archive.org/web/20120225055041/http://developers.sun.com/solaris/articles/cc_restrict.html

在"编译器可能假设......"方面做得很好.扩展该模式并将编译器可以做出的假设以及它们如何无法保持,每个示例都很棒.

c c99 strict-aliasing restrict-qualifier

17
推荐指数
1
解决办法
2539
查看次数

如何实现"_mm_storeu_epi64"没有别名问题?

(注意:虽然这个问题是关于"存储"的,但"加载"情况具有相同的问题并且是完全对称的.)

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)

以下是创建此类功能的六次尝试.

尝试#1

void _mm_storeu_epi64(int64_t *p, __m128i a) {
    _mm_storeu_si128(reinterpret_cast<__m128i *>(p), a);
}
Run Code Online (Sandbox Code Playgroud)

这似乎有我担心的严格别名问题.

尝试#2

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)

一般来说可能更好 …

c++ sse strict-aliasing intrinsics

17
推荐指数
1
解决办法
1506
查看次数

GCC和相同类型的数组之间的严格别名

上下文

以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,也就是说,pq不能为同一个内存位置的别名:

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)

c gcc strict-aliasing

17
推荐指数
2
解决办法
673
查看次数

将int放入char数组是否合法需要放置?

似乎有一些协议,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

17
推荐指数
1
解决办法
870
查看次数

为什么编译器不再使用严格的别名来优化此UB

谷歌严格别名的第一个结果之一就是这篇文章 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也没有优化它.

c c++ gcc clang strict-aliasing

16
推荐指数
2
解决办法
1054
查看次数

在实践中,工会,别名和打字:什么有效,有什么不可行?

我有一个问题,了解使用GCC的工会可以做什么和不可以做什么.我阅读了有关它的问题(特别是这里这里),但他们关注C++标准,我觉得C++标准和实践(常用的编译器)之间存在不匹配.

特别是,我最近在阅读有关编译标志-fstrict-aliasingGCC在线文档中发现了令人困惑的信息.它说:

-fstrict走样

允许编译器采用适用于正在编译的语言的最严格的别名规则.对于C(和C++),这将根据表达式的类型激活优化.特别地,假设一种类型的对象永远不会与不同类型的对象驻留在相同的地址,除非类型几乎相同.例如,a unsigned intcan可以是a int,但不是a void*或a double.字符类型可以别名为任何其他类型.特别注意这样的代码:

union a_union {
  int i;
  double d;
};

int f() {
  union a_union t;
  t.d = 3.0;
  return t.i;
}
Run Code Online (Sandbox Code Playgroud)

从不同的工会成员阅读的做法比最近写的那个(称为"打字式")很常见.即使使用-fstrict-aliasing,只要通过union类型访问内存,就允许类型为punning.因此,上面的代码按预期工作.

这是我认为我从这个例子和我的疑虑中理解的:

1)别名仅适用于相似类型或char

1)的后果:别名 - 正如文字暗示的那样 - 是你有一个值和两个成员来访问它(即相同的字节);

怀疑:当它们具有相同的字节大小时,两种类型是相似的吗?如果没有,什么是类似的类型?

1)对于非相似类型(无论这意味着什么)的后果,别名不起作用;

2)类型双关语是指我们读的不同于我们写的成员; 它是常见的,只要通过union类型访问内存,它就可以正常工作;

怀疑:在类型相似的特定情况下别名是什么类型?

我感到困惑,因为它表示unsigned int和double不相似,所以别名不起作用; 然后在示例中它是int和double之间的别名,它清楚地表明它按预期工作,但称之为类型 - 惩罚:不是因为类型是或不相似,而是因为它是从一个不写的成员读取.但是从一个没有写的成员那里读取的是我所理解的混淆(正如这个词所暗示的那样).我迷路了.

问题: 有人可以澄清别名和类型惩罚之间的区别,这两种技术的用途是如何在GCC中发挥作用的?编译器标志有什么作用?

c++ gcc strict-aliasing

16
推荐指数
2
解决办法
1374
查看次数