Maison  >  Article  >  Tutoriel système  >  Choses sur l'alignement des octets Linux

Choses sur l'alignement des octets Linux

WBOY
WBOYavant
2024-02-05 11:06:101048parcourir

Récemment, je travaillais sur un projet et j'ai rencontré un problème. Lorsque ThreadX exécuté sur ARM communique avec le DSP, il utilise une file d'attente de messages pour transmettre les messages (l'implémentation finale utilise des interruptions et des méthodes de mémoire partagée). Cependant, lors du fonctionnement réel, il a été constaté que ThreadX plantait souvent. Après enquête, il a été constaté que le problème réside dans le fait que la structure transmettant le message ne prend pas en compte l'alignement des octets.

J'aimerais régler les problèmes liés à l'alignement des octets en langage C et les partager avec vous.

1.Concept

L'alignement des octets est lié à l'emplacement des données en mémoire. Si l’adresse mémoire d’une variable est exactement un multiple entier de sa longueur, alors on dit qu’elle est naturellement alignée. Par exemple, sous un processeur 32 bits, en supposant que l'adresse d'une variable entière est 0x00000004, elle est alors naturellement alignée.

Comprenez d'abord ce que sont les bits, les octets et les mots

Nom Nom anglais Signification
bits bit 1 chiffre binaire s'appelle 1 bit
octets Octet 8 bits binaires sont appelés 1 octet
mots mot Une longueur fixe utilisée par les ordinateurs pour traiter les transactions en une seule fois

Longueur

Le nombre de bits dans un mot, la longueur du mot des ordinateurs modernes est généralement de 16, 32 ou 64 bits. (En général, la longueur des mots des systèmes à N bits est de N/8 octets.)

Différents processeurs peuvent traiter différents nombres de bits de données à la fois. Un processeur 32 bits peut traiter des données 32 bits à la fois, et un processeur 64 bits peut traiter des données 64 bits à la fois. la longueur du mot.

La longueur du mot est parfois appelée un mot. Dans un processeur 16 bits, un mot fait exactement deux octets, tandis que dans un processeur 32 bits, un mot fait quatre octets. Si nous prenons les caractères comme unités, il existe des caractères doubles (deux caractères) et des caractères quadruples (quatre caractères) vers le haut.

2. Règles d'alignement

Pour les types de données standard, son adresse doit uniquement être un multiple entier de sa longueur. Les types de données non standard sont alignés selon les principes suivants : Tableau : aligné selon les types de données de base. ceux-ci seront naturellement alignés. Union : aligné par le type de données qu'il contient avec la plus grande longueur. Structure : chaque type de données de la structure doit être aligné.

3. Comment limiter le nombre de bits d'alignement d'octets fixes ?

1. Par défaut

Par défaut, le compilateur C alloue de l'espace pour chaque variable ou unité de données en fonction de ses conditions aux limites naturelles. Généralement, les conditions aux limites par défaut peuvent être modifiées par les méthodes suivantes :

2. #pragma pack(n)

· En utilisant la directive #pragma pack (n), le compilateur C s'alignera sur n octets. · Utilisez la directive #pragma pack() pour annuler l'alignement des octets personnalisé.

#pragma pack(n) est utilisé pour définir les variables sur un alignement sur n octets. L'alignement sur n octets signifie qu'il existe deux situations pour le décalage de l'adresse de départ où la variable est stockée :

  1. Si n est supérieur ou égal au nombre d'octets occupés par la variable, alors le décalage doit respecter l'alignement par défaut
  2. Si n est inférieur au nombre d'octets occupés par le type de la variable, le décalage est un multiple de n et n'a pas besoin de respecter l'alignement par défaut.

La taille totale de la structure a également une contrainte. Si n est supérieur ou égal au nombre d'octets occupés par tous les types de variables membres, alors la taille totale de la structure doit être un multiple de l'espace occupé par le variable avec le plus grand espace ; sinon, elle doit être n multiples.

3. __attribut

De plus, il existe la méthode suivante : · __attribute((aligned (n))), qui permet d'aligner les membres de la structure sur la limite naturelle de n octets. Si la longueur d’un membre de la structure est supérieure à n, il est aligné en fonction de la longueur du plus grand membre. · attribute ((packed)), annule l'alignement optimisé de la structure lors de la compilation, et l'aligne en fonction du nombre réel d'octets occupés.

3. Compilation.align

Le code Assembly utilise généralement .align pour spécifier le nombre de bits d'alignement d'octets.

.align : utilisé pour préciser l'alignement des données, le format est le suivant :

.align [absexpr1, absexpr2]

Pallez les zones de stockage inutilisées avec des valeurs dans un certain alignement. La première valeur représente l'alignement, 4, 8, 16 ou 32. La deuxième valeur d'expression représente la valeur remplie.

四、为什么要对齐?

操作系统并非一个字节一个字节访问内存,而是按2,4,8这样的字长来访问。因此,当CPU从存储器读数据到寄存器,IO的数据长度通常是字长。如32位系统访问粒度是4字节(bytes), 64位系统的是8字节。当被访问的数据长度为n字节且该数据地址为n字节对齐时,那么操作系统就可以高效地一次定位到数据, 无需多次读取,处理对齐运算等额外操作。数据结构应该尽可能地在自然边界上对齐。如果访问未对齐的内存,CPU需要做两次内存访问。

字节对齐可能带来的隐患:

代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:

unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;

最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

五、举例

例1:os基本数据类型占用的字节数

首先查看操作系统的位数Choses sur lalignement des octets Linux

在64位操作系统下查看基本数据类型占用的字节数:

#include 

int main()
{
    printf("sizeof(char) = %ld\n", sizeof(char));
    printf("sizeof(int) = %ld\n", sizeof(int));
    printf("sizeof(float) = %ld\n", sizeof(float));
    printf("sizeof(long) = %ld\n", sizeof(long));                                      
    printf("sizeof(long long) = %ld\n", sizeof(long long));
    printf("sizeof(double) = %ld\n", sizeof(double));
    return 0;
}
Choses sur lalignement des octets Linux

例2:结构体占用的内存大小–默认规则

考虑下面的结构体占用的位数

struct yikou_s
{
    double d;
    char c;
    int i;
} yikou_t;

执行结果

sizeof(yikou_t) = 16

在内容中各变量位置关系如下:

其中成员C的位置Choses sur lalignement des octets Linux还受字节序的影响,有的可能在位置8

编译器给我们进行了内存对齐,各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量类型所占用的字节数的倍数, 且结构的大小为该结构中占用最大空间的类型所占用的字节数的倍数。

对于偏移量:变量type n起始地址相对于结构体起始地址的偏移量必须为sizeof(type(n))的倍数结构体大小:必须为成员最大类型字节的倍数

char: 偏移量必须为sizeof(char) 即1的倍数
int: 偏移量必须为sizeof(int) 即4的倍数
float: 偏移量必须为sizeof(float) 即4的倍数
double: 偏移量必须为sizeof(double) 即8的倍数

例3:调整结构体大小

我们将结构体中变量的位置做以下调整:

struct yikou_s
{
    char c;
    double d;
    int i;
} yikou_t;

执行结果

sizeof(yikou_t) = 24

各变量在内存中布局如下:

Choses sur lalignement des octets Linux

当结构体中有嵌套符合成员时,复合成员相对于结构体首地址偏移量是复合成员最宽基本类型大小的整数倍。

例4:#pragma pack(4)

#pragma pack(4)

struct yikou_s
{
    char c;
    double d;
    int i;
} yikou_t;
sizeof(yikou_t) = 16

例5:#pragma pack(8)

#pragma pack(8)

struct yikou_s
{
    char c;
    double d;
    int i;
} yikou_t;
sizeof(yikou_t) = 24

例6:汇编代码

举例:以下是截取的uboot代码中异常向量irq、fiq的入口位置代码:Choses sur lalignement des octets Linux

六、汇总实力

有手懒的同学,直接贴一个完整的例子给你们:

#include 
main()
{
struct A {
    int a;
    char b;
    short c;
};
 
struct B {
    char b;
    int a;
    short c;
};
struct AA {
   // int a;
    char b;
    short c;
};

struct BB {
    char b;
   // int a;
    short c;
}; 
#pragma pack (2) /*指定按2字节对齐*/
struct C {
    char b;
    int a;
    short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
 
 
 
#pragma pack (1) /*指定按1字节对齐*/
struct D {
    char b;
    int a;
    short c;
};
#pragma pack ()/*取消指定对齐,恢复缺省对齐*/
 
int s1=sizeof(struct A);
int s2=sizeof(struct AA);
int s3=sizeof(struct B);
int s4=sizeof(struct BB);
int s5=sizeof(struct C);
int s6=sizeof(struct D);
printf("%d\n",s1);
printf("%d\n",s2);
printf("%d\n",s3);
printf("%d\n",s4);
printf("%d\n",s5);
printf("%d\n",s6);
}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer