【问题标题】:access a mis-aligned structure member访问未对齐的结构成员
【发布时间】:2013-03-13 15:06:55
【问题描述】:

我想测试在 C 中访问一个未对齐的结构成员是否可以,请看代码

#include <stdio.h>

#pragma pack(1)  /* force 1 byte alignment */

/* either member b or member d is mis-aligned */
typedef struct
{
    int b;
    unsigned char c;
    unsigned int d;
}A ;


int main(int argc, char *argv[])
{
    A _a = {0};
    unsigned int *p = NULL;
    unsigned int *q = NULL;


    printf("addr of _a : 0x%08x, size of _a : %u\n", &_a, sizeof(_a));

    p = (unsigned int*)(&_a.b);
    q = (unsigned int*)(&_a.d);

    /* should this fail ? */
    (*p)++ , (*q)++;

    return 0;

}

假设由于内存访问未对齐导致的异常导致程序崩溃,但事实证明它运行得很好,在Linux 3.6.11(GCC 4.7.2),WinXP(MingW)中测试过, codepad 在线编译器(http://codepad.org/yOoc8ACG)

请解释结果,我猜操作系统已经做了一些事情来保存程序,仍然怀疑它是否可以在 VxWorks 或其他一些操作系统中运行

注意:代码在基于 Intel 的机器上运行!

提前致谢!

【问题讨论】:

  • 如果你在增加 p 和 q 后尝试将它们传递给另一个函数会发生什么?或者尝试访问这些位置的内存?
  • 行为未定义;根据编译器和体系结构,它可能会崩溃、给出不好的结果或“按预期”工作。见this questionanswer
  • 一个小问题:最好避免定义以下划线开头的标识符。有些保留给实现用于所有目的,有些保留在文件范围内。

标签: c compiler-construction operating-system access-violation memory-alignment


【解决方案1】:

假设您在 Intel 机器上运行。 x86 架构可以毫无问题地处理未对齐的访问。不过,您可能在其他架构上遇到问题。即使架构碰巧不支持未对齐的访问,您是对的,有时操作系统可以通过在某种 CPU 异常处理程序中使用单字节访问模拟未对齐的访问来为您解决问题。

我认为您也希望在您的测试程序中使用 (*p)++(*q)++

【讨论】:

    【解决方案2】:

    上面的一些答案说“既然是x86,它就不会失败”,这并不完全正确。虽然我知道没有操作系统可以做到这一点,但可以将 x86 处理器配置为在用户模式下的未对齐访问[不是在内核模式下,但发布的代码对我来说看起来像用户模式代码]。但是就像我说的那样,我知道没有一个操作系统会真正配置这个位,而且它很可能会破坏一些不希望处理器因未对齐的内存访问而出错的代码。

    未对齐访问的行为,无论您如何看待它,都是未定义的,或者充其量是实现定义的。这意味着这种操作的结果范围从“它就像你期望的那样工作”在频谱的一端到“程序崩溃”。在该范围的中间可能是“它不会崩溃,但它的行为也不像您预期​​的那样”的更糟糕的选项 - 例如,您可能会发现您获得了与之前对齐的地址相对应的值[lower memory address] 你期望获取的数据,或者获取确实正确执行,但是它需要比对齐的变体长 10 或 100 倍的时间,因为处理器在几个步骤中捕获并执行操作,然后从陷阱。如果您还运行多个线程,您可能还会发现对变量的更新不是以原子方式完成的,因此您得到的值是“一个的一半,另一个的一半”,这显然会导致非常奇怪的效果.这不是“事情有点不对劲,但不是以一种立即明显的方式”的潜在情况的结论性列表。

    我的建议是:不要搞乱对齐,尽最大努力不要编写访问未对齐元素的代码。它迟早会回来咬你的……

    【讨论】:

      【解决方案3】:

      基于 x86 的体系结构不寻常,因为它们的字访问指令可用于未对齐的地址。生成的操作速度较慢且非原子操作,但它可以工作。

      只要您的程序包含#pragma,它的含义(充其量)就是实现定义的。一般来说,将数据成员的地址分配给unsigned int* 变量将使实现“忘记”它可能未对齐,因此它不会为未对齐的加载发出代码。因此,在重要的架构上,*p*q(或两者)都不起作用。

      【讨论】:

        【解决方案4】:

        结果取决于架构和内核配置。通常在 x86 上,您可以访问未对齐的数据,但会带来一些性能损失。这主要是由于与旧系列 CPU 的兼容性。

        在 ARM 和 SPARC 上,行为取决于内核配置。操作系统可以禁止、允许甚至模拟这种未对齐的数据访问。在后一种情况下,硬件异常会被内核拦截,并使用某些操作系统的代码进行模拟。

        随着编译器版本的到位,这变得更加困难。例如,现代 GCC 会生成一个特殊代码,如果它发现数据未对齐,则可以以非原子方式(即使用多个指令)访问未对齐的数据。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-08-07
          • 1970-01-01
          • 1970-01-01
          • 2014-11-30
          • 1970-01-01
          • 1970-01-01
          • 2017-09-19
          • 1970-01-01
          相关资源
          最近更新 更多