Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/64.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C未定义的行为。严格的别名规则,还是不正确的对齐?_C_Gcc_Memory Alignment_Strict Aliasing - Fatal编程技术网

C未定义的行为。严格的别名规则,还是不正确的对齐?

C未定义的行为。严格的别名规则,还是不正确的对齐?,c,gcc,memory-alignment,strict-aliasing,C,Gcc,Memory Alignment,Strict Aliasing,我无法解释此程序的执行行为: #include <string> #include <cstdlib> #include <stdio.h> typedef char u8; typedef unsigned short u16; size_t f(u8 *keyc, size_t len) { u16 *key2 = (u16 *) (keyc + 1); size_t hash = len; len = len / 2;

我无法解释此程序的执行行为:

#include <string> 
#include <cstdlib> 
#include <stdio.h>

typedef char u8;
typedef unsigned short u16;

size_t f(u8 *keyc, size_t len)
{
    u16 *key2 = (u16 *) (keyc + 1);
    size_t hash = len;
    len = len / 2;

    for (size_t i = 0; i < len; ++i)
        hash += key2[i];
    return hash;
}

int main()
{
    srand(time(NULL));
    size_t len;
    scanf("%lu", &len);
    u8 x[len];
    for (size_t i = 0; i < len; i++)
        x[i] = rand();

    printf("out %lu\n", f(x, len));
}
#包括
#包括
#包括
typedef char u8;
typedef无符号短u16;
尺寸(u8*keyc,尺寸长度)
{
u16*key2=(u16*)(keyc+1);
大小\u t哈希=len;
len=len/2;
对于(尺寸i=0;i
因此,当使用-O3和gcc编译它,并使用参数25运行时,它会引发一个segfault。如果没有优化,它可以正常工作。我已经对它进行了反汇编:它正在进行矢量化,编译器假设
key2
数组以16字节对齐,因此它使用
movdqa
。显然是UB,尽管我无法解释。我知道严格别名规则,但情况并非如此(我希望如此),因为据我所知,严格别名规则不适用于
char
s。为什么gcc假设这个指针是对齐的?即使进行了优化,Clang也可以很好地工作

编辑

我将
无符号字符
更改为
字符
,并删除了
常量
,它仍然存在故障

编辑2


我知道这段代码不好,但就我所知,它应该可以正常工作,因为我知道严格的别名规则。冲突具体在哪里?

将指向对象的指针别名为指向字符的指针是合法的,然后迭代原始对象中的所有字节

当指向char的指针实际指向一个对象(通过前面的操作获得)时,将其转换回指向原始类型的指针是合法的,并且标准要求您返回原始值

但是,将指向char的任意指针转换为指向object的指针并取消对获取的指针的引用违反了严格的别名规则并调用了未定义的行为

因此,在您的代码中,以下行是UB:

const u16 *key2 = (const u16 *) (keyc + 1); 
// keyc + 1 did not originally pointed to a u16: UB

该代码确实违反了严格的别名规则。但是,不仅存在混叠冲突,而且由于混叠冲突,崩溃不会发生。发生这种情况的原因是
无符号短
指针对齐不正确
;如果结果未适当对齐,则即使是指针转换本身也未定义

:

1在以下情况下,该行为未定义:

  • 两种指针类型之间的转换会产生错误对齐的结果(6.3.2.3)
用言语

[…]如果所引用类型的结果指针未正确对齐[68],则行为未定义。[……]

unsigned short
在您的实现(x86-32和x86-64)上有2的对齐要求,您可以使用它进行测试

_Static_assert(_Alignof(unsigned short) == 2, "alignof(unsigned short) == 2");
但是,您正在强制
u16*key2
指向未对齐的地址:

u16 *key2 = (u16 *) (keyc + 1);  // we've already got undefined behaviour *here*!
有无数程序员坚持认为,无论在哪里,在x86-32和x86-64上,未对齐的访问都能在实践中得到保证,而且在实践中不会有任何问题——好吧,他们都错了

基本上,编译器会注意到

for (size_t i = 0; i < len; ++i)
     hash += key2[i];
翻译单元2

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <inttypes.h>

size_t f(uint16_t *keyc, size_t len);

struct mystruct {
    uint8_t padding;
    uint16_t contents[100];
} __attribute__ ((packed));

int main(void)
{
    struct mystruct s;
    size_t len;

    srand(time(NULL));
    scanf("%zu", &len);

    char *initializer = (char *)s.contents;
    for (size_t i = 0; i < len; i++)
       initializer[i] = rand();

    printf("out %zu\n", f(s.contents, len));
}
请注意,这里没有别名冲突。唯一的问题是未对齐的
uint16\u t*keyc

使用
-fsanize=undefined
会产生以下错误:

unit1.c:10:21: runtime error: load of misaligned address 0x7ffefc2d54f1 for type 'uint16_t', which requires 2 byte alignment
0x7ffefc2d54f1: note: pointer points here
 00 00 00  01 4e 02 c4 e9 dd b9 00  83 d9 1f 35 0e 46 0f 59  85 9b a4 d7 26 95 94 06  15 bb ca b3 c7
              ^ 

除非代码做了一些事情来确保字符类型的数组是对齐的,否则不应该特别期望它是对齐的

如果注意对齐,代码只取一次地址,将其转换为另一种类型的指针,并且从不通过非从后一种指针派生的任何方式访问存储器,那么为低级编程设计的实现将存储器视为抽象缓冲区应该没有特别的困难。由于这种处理并不困难,并且对于某些类型的低级编程来说是必要的(例如,在malloc()可能不可用的上下文中实现内存池),因此不支持这种构造的实现不应该声称适合低级编程

因此,在为低级编程而设计的实现中,您所描述的构造将允许将适当对齐的阵列视为非类型化存储。不幸的是,没有简单的方法来识别这样的实现,因为主要为低级编程设计的实现常常无法列出作者认为这样的实现显然是以环境的方式行为的所有情况(因此他们确实这样做了),而那些设计专注于其他目的的人可能会声称适合于低级编程,即使他们的行为不适合于该目的


该标准的作者认识到C语言对于不可移植程序是一种有用的语言,并明确表示他们不希望排除它作为“高级汇编程序”的使用。然而,他们预计,用于各种目的的实现将支持流行的扩展,以促进这些目的,而不考虑标准是否要求他们这样做,因此没有必要让标准解决这些问题。因为这种意图被降级为基本原理而非立场然而,一些编译器编写者将该标准视为程序员对实现的所有期望的完整描述,因此可能不支持低级概念,如使用静态或自动持续时间对象作为有效的非类型缓冲区。

为excellen提供更多信息和常见陷阱坦斯瓦
% gcc -O3 unit1.c unit2.c
% ./a.out
25
zsh: segmentation fault (core dumped)  ./a.out
unit1.c:10:21: runtime error: load of misaligned address 0x7ffefc2d54f1 for type 'uint16_t', which requires 2 byte alignment
0x7ffefc2d54f1: note: pointer points here
 00 00 00  01 4e 02 c4 e9 dd b9 00  83 d9 1f 35 0e 46 0f 59  85 9b a4 d7 26 95 94 06  15 bb ca b3 c7
              ^ 
char* raw = receiveData();
int32_t sum = 0;
uint16_t len = *((uint16_t*)raw);
int32_t* data = (int32_t*)(raw2 + 2);
for(size_t i=0; i<len; ++i) sum += data[i];
char* raw = receiveData();
int32_t foo = readInt(raw); raw+=4;
bool foo = readBool(raw); raw+=1;
int16_t foo = readShort(raw); raw+=2;
...
int32_t readInt(char* ptr){
  int32_t result = *((int32_t*) ptr);
  #if BIG_ENDIAN
  result = byteswap(result);
  #endif
}
#include <cstdint>
#include <boost/endian/arithmetic.hpp>


__attribute__ ((noinline)) size_t f(boost::endian::little_uint16_t *keyc, size_t len)
{
    size_t hash = 0;
    for (size_t i = 0; i < len; ++i)
        hash += keyc[i];
    return hash;
}

struct mystruct {
    uint8_t padding;
    boost::endian::little_uint16_t contents[100];
};

int main(int argc, char** argv)
{
    mystruct s;
    size_t len = argc*25;

    for (size_t i = 0; i < len; i++)
       s.contents[i] = i * argc;

    return f(s.contents, len) != 300;
}
class little_uint16_t{
  char buffer[2];
  uint16_t value(){
    #if IS_x86
      uint16_t value = *reinterpret_cast<uint16_t*>(buffer);
    #else
    ...
    #endif
    #if BIG_ENDIAN
    swapbytes(value);
    #endif
    return value;
};