对于不同的数据类型,内存对齐是否不同

时间:2010-01-14 19:48:58

标签: c alignment

C中的不同数据类型(例如charshortintlongfloatdouble是否具有不同的内存对齐方式边界?在32位字对齐字节可寻址操作系统中,如何访问charshort与访问intfloat不同?在这两种情况下,CPU是否读取完整的32位字?当int不在边界时会发生什么?如何在任何内存地址读取char

8 个答案:

答案 0 :(得分:6)

正如其他人所指出的那样,简短的回答是编译器会为它编译的架构做最好的事情。它可以将它们与原始单词大小对齐。它可能不会。这是一个展示这一点的示例程序:

#include <iostream>

int main()
{
    using namespace std;

    char c;
    short s;
    int i;

    cout << "sizeof(char): " << sizeof(char) << endl;
    cout << "sizeof(short): " << sizeof(short) << endl;
    cout << "sizeof(int): " << sizeof(int) << endl;

    cout << "short is " << (int)&s - (int)&c << " bytes away from a char" << endl;
    cout << "int is " << (int)&i - (int)&s << " bytes away from a short" << endl;
}

输出:

sizeof(char): 1
sizeof(short): 2
sizeof(int): 4
short is 1 bytes away from a char
int is 4 bytes away from a short

正如您所看到的,它在int和short之间添加了一些填充。它并没有打扰短片。在其他情况下,反之亦然。优化规则很复杂。

并且,警告:编译器比你聪明。除非你有一个非常非常好的理由,否则不要使用填充和对齐。只要相信编译器正在做的事情是正确的。

答案 1 :(得分:5)

这取决于编译器和您定义变量的方式。大多数编译器的默认行为是将变量对齐,以便在给定平台上实现最快的访问。对齐变量可以为您提供最佳性能。

然而,像gcc这样的编译器提供compiler specific directives可以用来“打包”不同类型的相邻变量(以及因此大小),以节省成本来节省内存(但这就是你得到的)通过使用包装指令来决定。)见question.

当读取char / short时,CPU可能会读取一个完整的32位字(可能还有更多来获取整个缓存行)。

答案 2 :(得分:4)

很多问题......

  

C中的不同数据类型如char,short,int,long,float,double是否有不同的内存对齐边界?

是。确切的对齐边界是特定于编译器的,有些可以让您更改它们打包struct的方式。 (最好插入填充字段,以免让它成为一个问题。)

  

在32位字对齐的字节可寻址操作系统中,如何访问char或short来访问int或float?

实际上,这取决于架构。我已经看到一些在总线上有字节启用线路,并将使用它们来访问他们想要的内存部分。在其他情况下,非I / O内存访问会导致读取或写入整个缓存行。

  

在这两种情况下,CPU是否读取完整的32位字?

不一定。使用字节启用,您不必读取完整的32位字。 Byte Enables还允许您在&gt; 8位架构上写入单个字节,而无需执行读 - 修改 - 写入。

  

当int不在边界时会发生什么?

某些体系结构(例如x86,IIRC)将执行多次访问并为您加入这些部分。其他(例如PowerPC)将生成总线错误或类似的异常。

  

如何在任何内存地址读取字符?

因为地址是按体系结构中的字节量化的。并非所有架构都是如此。 DSP以字对齐指针而闻名,即指针是字地址,而不是字节地址。 (我必须为其中一个编写一个串口驱动程序。sizeof(char) == sizeof(short) == 1 == 16位。所以你必须在浪费一半RAM的简单代码和大量字节打包/解包代码之间做出选择。)

答案 3 :(得分:2)

简短回答:这取决于您的编译器和架构。大多数编译器都有某种命令行选项或#pragma,您可以使用它来手动指定或更改变量的对齐方式。

我曾经使用过这样的东西来研究各种类型的数据对齐:

union {
  struct {
    char one;
    char two;
    char three;
    char four;
  } chars;
  struct {
    short one;
    short two;
    short three;
    short four;
  } shorts;
  struct {
    int one;
    int two;
    int three;
    int four;
  } ints;
  struct {
    double one;
    double two;
    double three;
    double four;
  } doubles;
  /* etc, etc */
} many_types;

通过查看每个结构成员与sizeof()该成员的地址,您可以了解编译器如何对齐不同的数据类型。

答案 4 :(得分:2)

你可能会研究这个程序的输出 - 在运行MacOS X 10.6.2的Intel Mac上为32位和64位编译。

/*
@(#)File:           $RCSfile: typesize.c,v $
@(#)Version:        $Revision: 1.7 $
@(#)Last changed:   $Date: 2008/12/21 18:25:17 $
@(#)Purpose:        Structure sizes/alignments
@(#)Author:         J Leffler
@(#)Copyright:      (C) JLSS 1990,1997,2004,2007-08
@(#)Product:        :PRODUCT:
*/

#include <stdio.h>
#include <time.h>
#include <stddef.h>
#if __STDC_VERSION__ >= 199901L
#include <inttypes.h>
#endif /* __STDC_VERSION__ */

#define SPRINT(x)   printf("%2u = sizeof(" #x ")\n", (unsigned int)sizeof(x))

int main(void)
{
    /* Basic Types */
    SPRINT(char);
    SPRINT(unsigned char);
    SPRINT(short);
    SPRINT(unsigned short);
    SPRINT(int);
    SPRINT(unsigned int);
    SPRINT(long);
    SPRINT(unsigned long);
#if __STDC_VERSION__ >= 199901L
    SPRINT(long long);
    SPRINT(unsigned long long);
    SPRINT(uintmax_t);
#endif /* __STDC_VERSION__ */
    SPRINT(float);
    SPRINT(double);
    SPRINT(long double);
    SPRINT(size_t);
    SPRINT(ptrdiff_t);
    SPRINT(time_t);

    /* Pointers */
    SPRINT(void *);
    SPRINT(char *);
    SPRINT(short *);
    SPRINT(int *);
    SPRINT(long *);
    SPRINT(float *);
    SPRINT(double *);

    /* Pointers to functions */
    SPRINT(int (*)(void));
    SPRINT(double (*)(void));
    SPRINT(char *(*)(void));

    /* Structures */
    SPRINT(struct { char a; });
    SPRINT(struct { short a; });
    SPRINT(struct { int a; });
    SPRINT(struct { long a; });
    SPRINT(struct { float a; });
    SPRINT(struct { double a; });
    SPRINT(struct { char a; double b; });
    SPRINT(struct { short a; double b; });
    SPRINT(struct { long a; double b; });
    SPRINT(struct { char a; char b; short c; });
    SPRINT(struct { char a; char b; long c; });
    SPRINT(struct { short a; short b; });
    SPRINT(struct { char a[3]; char b[3]; });
    SPRINT(struct { char a[3]; char b[3]; short c; });
    SPRINT(struct { long double a; });
    SPRINT(struct { char a; long double b; });
#if __STDC_VERSION__ >= 199901L
    SPRINT(struct { char a; long long b; });
#endif /* __STDC_VERSION__ */

    return(0);
}

64位编译的输出:

 1 = sizeof(char)
 1 = sizeof(unsigned char)
 2 = sizeof(short)
 2 = sizeof(unsigned short)
 4 = sizeof(int)
 4 = sizeof(unsigned int)
 8 = sizeof(long)
 8 = sizeof(unsigned long)
 8 = sizeof(long long)
 8 = sizeof(unsigned long long)
 8 = sizeof(uintmax_t)
 4 = sizeof(float)
 8 = sizeof(double)
16 = sizeof(long double)
 8 = sizeof(size_t)
 8 = sizeof(ptrdiff_t)
 8 = sizeof(time_t)
 8 = sizeof(void *)
 8 = sizeof(char *)
 8 = sizeof(short *)
 8 = sizeof(int *)
 8 = sizeof(long *)
 8 = sizeof(float *)
 8 = sizeof(double *)
 8 = sizeof(int (*)(void))
 8 = sizeof(double (*)(void))
 8 = sizeof(char *(*)(void))
 1 = sizeof(struct { char a; })
 2 = sizeof(struct { short a; })
 4 = sizeof(struct { int a; })
 8 = sizeof(struct { long a; })
 4 = sizeof(struct { float a; })
 8 = sizeof(struct { double a; })
16 = sizeof(struct { char a; double b; })
16 = sizeof(struct { short a; double b; })
16 = sizeof(struct { long a; double b; })
 4 = sizeof(struct { char a; char b; short c; })
16 = sizeof(struct { char a; char b; long c; })
 4 = sizeof(struct { short a; short b; })
 6 = sizeof(struct { char a[3]; char b[3]; })
 8 = sizeof(struct { char a[3]; char b[3]; short c; })
16 = sizeof(struct { long double a; })
32 = sizeof(struct { char a; long double b; })
16 = sizeof(struct { char a; long long b; })

32位编译的输出:

 1 = sizeof(char)
 1 = sizeof(unsigned char)
 2 = sizeof(short)
 2 = sizeof(unsigned short)
 4 = sizeof(int)
 4 = sizeof(unsigned int)
 4 = sizeof(long)
 4 = sizeof(unsigned long)
 8 = sizeof(long long)
 8 = sizeof(unsigned long long)
 8 = sizeof(uintmax_t)
 4 = sizeof(float)
 8 = sizeof(double)
16 = sizeof(long double)
 4 = sizeof(size_t)
 4 = sizeof(ptrdiff_t)
 4 = sizeof(time_t)
 4 = sizeof(void *)
 4 = sizeof(char *)
 4 = sizeof(short *)
 4 = sizeof(int *)
 4 = sizeof(long *)
 4 = sizeof(float *)
 4 = sizeof(double *)
 4 = sizeof(int (*)(void))
 4 = sizeof(double (*)(void))
 4 = sizeof(char *(*)(void))
 1 = sizeof(struct { char a; })
 2 = sizeof(struct { short a; })
 4 = sizeof(struct { int a; })
 4 = sizeof(struct { long a; })
 4 = sizeof(struct { float a; })
 8 = sizeof(struct { double a; })
12 = sizeof(struct { char a; double b; })
12 = sizeof(struct { short a; double b; })
12 = sizeof(struct { long a; double b; })
 4 = sizeof(struct { char a; char b; short c; })
 8 = sizeof(struct { char a; char b; long c; })
 4 = sizeof(struct { short a; short b; })
 6 = sizeof(struct { char a[3]; char b[3]; })
 8 = sizeof(struct { char a[3]; char b[3]; short c; })
16 = sizeof(struct { long double a; })
32 = sizeof(struct { char a; long double b; })
12 = sizeof(struct { char a; long long b; })

你可以用结构玩各种游戏。关键是不同类型的对齐要求确实不同。根据平台的不同,您可能会有或多或少的严格要求。 SPARC很挑剔;如果你做错了访问,英特尔往往会做更多的工作(所以它很慢,但有效);旧的DEC Alpha芯片(我认为MIPS RISC芯片)可以切换到不同的行为,或者更高效,总是需要对齐访问,或者效率更低,无法模仿英特尔芯片的功能。

答案 5 :(得分:1)

在许多平台上,错误对齐的内存访问会带来性能损失,甚至可能导致程序中断。

例如,在x86上,如果设置了SIGBUSEFLAGS.AC,则通过错误对齐的指针访问内存可能会导致CR0.AM被提升(请参阅this answer)。< / p>

答案 6 :(得分:1)

是的,它们确实有不同的内存对齐要求。在现实生活中,通常假定/需要在与该类型的 size 相同的边界处对齐特定类型,尽管理论上 size 和<的概念em> alignment 彼此之间没有任何联系。

在某些特定情况下,平台可能需要将一条数据对齐到比相应数据类型的大小更严格(更大)的边界。出于性能原因,例如,或出于某些其他特定于平台的原因,这可能是必需的。

如果数据未对齐,则行为取决于平台。在某些硬件平台上,尝试访问未对齐的数据将导致崩溃(例如,Sun机器)。在其他硬件平台上,它可能会导致访问效率和/或原子性的轻微损失,而不会产生其他不利影响(例如,英特尔x86机器)。

这里值得一提的一个重要细节是,从迂腐的角度来看,对于C程序,术语 platform 是指编译器提供的环境,而不是硬件提供的环境。编译器总是可以自由地实现一个抽象层,它将C程序与底层硬件平台隔离开来,完全(或几乎完全)隐藏任何硬件强加的要求。例如,即使底层硬件平台确实强加了这样的要求,也可以实现一个将从C程序中删除任何对齐要求的实现。然而在实践中,出于对C语言哲学很重要的效率考虑,大多数时候(如果不总是)硬件对齐要求也适用于C程序。

答案 7 :(得分:0)

是。在一个典型但不通用的例子中:

1个字
  2短期   4 int
  4浮子
  8双

CPU的作用是CPU和编译器的业务。在约束的CPU上,编译器会考虑到这一点。在RISC-y芯片上,CPU可能必须加载32位并移位和屏蔽以获得字符。