2

我想测试是否可以访问 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 的机器上运行!

提前致谢 !

4

4 回答 4

1

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

我想你也想(*p)++(*q)++你的测试程序中。

于 2013-03-13T15:08:17.430 回答
1

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

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

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

于 2013-03-13T15:48:51.230 回答
0

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

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

于 2013-03-13T15:12:21.680 回答
0

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

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

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

于 2013-03-13T15:21:55.223 回答