代码之家  ›  专栏  ›  技术社区  ›  Jeremy Friesner

只要你不去引用一个未对齐的指针,它是否定义得很好?

  •  33
  • Jeremy Friesner  · 技术社区  · 8 年前

    我有一些C代码,可以解析来自网络的打包/未添加的二进制数据。

    这段代码在intel/x86下运行良好,但当我在arm下编译时,它常常会崩溃。

    正如您可能已经猜到的,罪魁祸首是未对齐的指针——特别是,解析代码将执行如下可疑操作:

    uint8_t buf[2048];
    [... code to read some data into buf...]
    int32_t nextWord = *((int32_t *) &buf[5]);  // misaligned access -- can crash under ARM!
    

    …很明显,它不会在空中飞行,所以我把它改成这样:

    uint8_t buf[2048];
    [... code to read some data into buf...]
    int32_t * pNextWord = (int32_t *) &buf[5];
    int32 nextWord;
    memcpy(&nextWord, pNextWord, sizeof(nextWord));  // slower but ARM-safe
    

    我的问题(从语言律师的角度)是:我的“手臂固定”方法是否在C语言规则下定义良好?

    我担心的是,即使只是一个未对齐的int32指针也可能足以调用未定义的行为,即使我从未直接取消对它的引用。(如果我的担心是正确的,我想我可以通过改变 pNextWord 的类型来自 (const int32_t *) (const char *) ,但我宁愿不这样做,除非确实有必要这样做,因为这将意味着手动执行一些指针跨距算法)

    4 回复  |  直到 8 年前
        1
  •  21
  •   Todd Lehman ipmcc    6 年前

    不,新代码仍然有未定义的行为。 C11 6.3.2.3p7 以下内容:

    1. 指向对象类型的指针可以转换为指向其他对象类型的指针。如果结果指针未正确对齐 68) 对于引用的类型,行为未定义。[…]

    它没有说明任何关于取消引用指针的内容—甚至转换也有未定义的行为。


    实际上,您假设的修改后的代码是 手臂 -安全可能不公平 情报 -安全。已知编译器为 Intel that can crash on unaligned access 是的。虽然不是在链接的情况下,但聪明的编译器可能会将转换作为 证明 地址确实是对齐的,并使用专门的代码 memcpy 是的。


    除了对齐之外,您的第一个摘录还受到严格的别名冲突的影响。 C11 6.5p7 以下内容:

    1. 对象的存储值只能由具有以下类型之一的左值表达式访问:88)
      • 与对象的有效类型兼容的类型,
      • 与有效类型兼容的类型的限定版本 目标,
      • 一种有符号或无符号类型 对应于对象的有效类型,
      • 一种 是否有符号或无符号类型对应于 对象的有效类型,
      • 聚合或联合类型 在其成员中包括上述类型之一 (递归地包括子集合的一个成员或包含 联合),或
      • 字符类型。

    从阵列开始 buf[2048] 是静态的 打字的 ,每个元素 char ,因此元素的有效类型是 烧焦 ;您可以访问数组的内容 只有 作为角色,而不是作为 int32_t S.

    也就是说,甚至

    int32_t nextWord = *((int32_t *) &buf[_Alignof(int32_t)]);
    

    行为不明确。

        2
  •  8
  •   lee qiaoping    8 年前

    为了安全地跨编译器/平台解析多字节整数, 可以提取每个字节,并根据尾数将它们组合成整数。例如,要从big endian buffer读取4字节整数:

    uint8_t* buf = any address;
    
    uint32_t val = 0;
    uint32_t  b0 = buf[0];
    uint32_t  b1 = buf[1];
    uint32_t  b2 = buf[2];
    uint32_t  b3 = buf[3];
    
    val = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
    
        3
  •  4
  •   supercat    8 年前

    有些编译器可能认为指针永远不会保存与其类型不正确对齐的值,并执行依赖于该值的优化。作为一个简单的例子,请考虑:

    void copy_uint32(uint32_t *dest, uint32_t *src)
    {
      memcpy(dest, src, sizeof (uint32_t));
    }
    

    如果两者 dest src 保留32位对齐地址,即使在不支持未对齐访问的平台上,上述函数也可以优化为一个加载和一个存储。如果已声明函数接受类型为的参数 void* 但是,在未对齐的32位访问与字节访问、移位和按位操作序列的行为不同的平台上,不允许进行这种优化。

        4
  •  2
  •   dbush    8 年前

    正如antti haapala的回答中所提到的,当得到的指针没有正确对齐时,简单地将指针转换为另一种类型会调用c标准第6.3.2.3p7节中未定义的行为。

    修改后的代码只使用 pNextWord 传给 memcpy ,在那里它被转换成 void * ,所以您甚至不需要类型的变量 uint32_t * 是的。只需将要从中读取的缓冲区中第一个字节的地址传递到 记忆 是的。那你就根本不用担心对齐问题了。

    uint8_t buf[2048];
    [... code to read some data into buf...]
    int32_t nextWord;
    memcpy(&nextWord, &buf[5], sizeof(nextWord));