4
  1. 我听说对齐 int 的读取和写入是原子且安全的,我想知道系统何时使非 malloc 全局变量不对齐,而不是打包结构和强制转换/指针算术字节缓冲区?

  2. [X86-64 linux] 在我所有的正常情况下,系统总是选择不会被撕裂的整数位置,例如,一个字上的两个字节,另一个字上的另外两个字节。任何人都可以发布强制全局变量未对齐地址的程序/片段(C 或程序集),以便整数被撕裂,系统必须使用两次读取来加载一个整数值吗?

    当我打印下面的程序时,地址彼此接近,因此多个变量在 64 位内,但从来没有看到过字撕裂(系统或编译器中的智能?)

    #include <stdio.h>
    int a;
    char  b;
    char c;
    int      d;
    int e = 0;
    
    
    int isaligned(void *p, int N)
    {
        if (((int)p % N) == 0)
            return 1;
        else
            return 0;
    }
    
    int main()
    {
    
        printf("processor is %d byte mode \n", sizeof(int *));
        printf ( "a=%p/b=%p/c=%p/d=%p/f=%p\n", &a, &b, &c, &d, &e );
    
        printf ( " check for 64bit alignment of test result of 0x80 = %d \n", isaligned( 0x80, 64 ));
        printf ( " check for 64bit alignment of a result = %d \n", isaligned( &a, 64 ));
        printf ( " check for 64bit alignment of d  result = %d \n", isaligned( &e, 64 ));
    
    return 0;}
    

    输出:

    processor is 8 byte mode 
    a=0x601038/b=0x60103c/c=0x60103d/d=0x601034/f=0x601030
     check for 64bit alignment of test result of 0x80 = 1 
     check for 64bit alignment of a result = 0 
     check for 64bit alignment of d  result = 0 
    
  3. 在上述情况下如何读取 char ?它是否从 8 字节对齐边界(在我的情况下为 0x601030 )读取,然后转到 0x60103c ?

  4. 内存访问粒度总是字长不是吗?

谢谢。

4

5 回答 5

4

1)是的,不能保证未对齐的访问是原子的,因为[至少有时,在某些类型的处理器上]数据可能会作为两个单独的写入写入 - 例如,如果您跨越内存页面边界 [我是不是说虚拟内存的 4KB 页面,我说的是 DDR2/3/4 页面,它是总内存大小的一小部分,通常是 16Kbits 乘以实际内存芯片的宽度 - 这将取决于记忆棒本身]。同样,在 x86 以外的其他处理器上,您会遇到读取未对齐内存的陷阱,这将导致程序中止,或者在软件中将读取模拟为多次读取以“修复”未对齐读取。

2)您始终可以通过以下方式创建未对齐的内存区域:

char *ptr = malloc(sizeof(long long) * number+1);
long long *unaligned = (long long *)&ptr[2];

for(i = 0; i < number; i++)
   temp = unaligned[i]; 

顺便说一句,您的对齐检查检查地址是否对齐到 64 字节,而不是 64 位。您必须除以 8 以检查它是否与 64 位对齐。

3) char 是单字节读取,地址将在字节本身的实际地址上。实际执行的内存读取可能是针对完整的缓存线,从目标地址开始,然后循环,例如:

0x60103d 是目标地址,因此处理器将从我们想要的 64 位字开始读取 32 字节的缓存行:0x601038(一旦完成,处理器就会继续执行下一条指令 - 同时下一次读取将执行填充缓存线),然后缓存线填充0x601020、0x601028、0x601030。但是如果我们关闭缓存[如果您希望您的 3GHz 最新 x86 处理器比 66MHz 486 稍慢,禁用缓存是实现此目的的好方法],处理器将只读取 0x60103d 处的一个字节。

4) 不是在 x86 处理器上,它们具有字节寻址 - 但对于普通内存,读取是在高速缓存线的基础上完成的,如上所述。

还要注意,“可能不是原子的”与“不会是原子的”完全不同——所以你可能很难按意愿让它出错——你真的需要得到两个不同的所有时间线程恰到好处,并且跨越缓存线,跨越内存页面边界等等以使其出错 - 如果您不希望它发生,就会发生这种情况,但试图让它出错可能很难[相信我,我去过那里,做到了]。

于 2013-02-01T00:01:49.447 回答
2

1)这个答案是特定于平台的。但是,一般来说,编译器会对齐变量,除非您强制它这样做。

2) 在 32 位 CPU 上运行时,以下内容需要两次读取才能加载一个变量:

uint64_t huge_variable;

该变量比寄存器大,因此需要多次操作才能访问。你也可以通过使用打包结构来做类似的事情:

struct unaligned __attribute__ ((packed))
{
    char buffer[2];
    int  unaligned;
    char buffer2[2];
} sample_struct;

3)这个答案是特定于平台的。某些平台的行为可能与您描述的一样。一些平台具有能够获取数据的半寄存器或四分之一寄存器的指令。我建议检查编译器发出的程序集以获取更多详细信息(确保首先关闭所有编译器优化)。

4) C 语言允许您以字节大小的粒度访问内存。这是如何在后台实现的,以及您的 CPU 获取多少数据来读取单个字节是特定于平台的。对于许多 CPU,这与通用寄存器的大小相同。

于 2013-01-31T23:05:38.550 回答
2
  1. 在这些情况之外,它可能不会。

  2. 在组装中它是微不足道的。就像是:

         .org 0x2
    myglobal:
         .word SOME_NUMBER
    

    但是在 Intel 上,处理器可以安全地读取未对齐的内存。它可能不是原子的,但从生成的代码中可能并不明显。

  3. 英特尔,对吧?Intel ISA 具有单字节读/写操作码。反汇编你的程序,看看它在使用什么。

  4. 不一定 - 您可能在内存字长和处理器字长之间存在不匹配。

于 2013-01-31T23:03:08.670 回答
0
  1. C 标准保证malloc(3)返回符合最严格对齐要求的内存区域,因此在这种情况下不会发生这种情况。如果有未对齐的数据,它可能是按片段读取/写入的(这取决于架构提供的确切保证)。
  2. 在某些架构上允许未对齐的访问,在其他架构上这是一个致命错误。如果允许,它通常对齐访问慢得多;当不允许时,编译器必须将这些部分拼接在一起,这甚至慢得多。
  3. 通常允许字符(实际上是字节)具有任何字节地址。在这种情况下,使用字节的指令只是获取/存储单个字节。
  4. 不,内存访问是根据数据的宽度。但真正的内存访问是根据高速缓存行(为此在CPU 高速缓存上读取)。
于 2013-02-01T00:06:34.060 回答
0

如果不调用未定义的行为,非对齐对象永远不会存在。换句话说,没有任何动作序列,所有动作都具有明确定义的行为,程序可以采取这将导致非对齐指针的存在。特别是,没有可移植的方法让编译器为您提供未对齐的对象。最接近的是许多编译器具有的“打包结构”,但这仅适用于结构成员,而不适用于独立对象。

此外,没有办法在可移植 C 中测试对齐性。您可以使用实现定义的指针到整数的转换并检查低位,但没有基本要求“对齐”指针在低位中具有零,或者转换为整数后的低位甚至对应于指针的“最低有效”位,无论这意味着什么。换句话说,指针和整数之间的转换不需要进行算术运算。

如果您真的想制作一些未对齐的指针,假设alignof(int)>1,最简单的方法是:

char buf[2*sizeof(int)+1];
int *p1 = (int *)buf, *p2 = (int *)(buf+sizeof(int)+1);

如果大于 1 两者bufbuf+sizeof(int)+1同时对齐是不可能的。因此,两个强制转换中的至少一个应用于未对齐的指针,调用未定义的行为,典型结果是未对齐的指针。intalignof(int)(int *)

于 2013-02-01T00:56:51.533 回答