malloc 和 C 对齐:这种手工优化安全吗?
malloc and C alignment: is this hand-made optimization safe?
我把整个代码放在了不同的文件中(.h
和 .c
文件)
typedef unsigned char ubyte;
typedef unsigned int uint;
#include<stdbool.h>
typedef struct Cube {
ubyte n;
ubyte e;
ubyte s;
ubyte w;
} Cube;
typedef struct Piece {
Cube c;
bool is_main;
char offset_n;
char offset_s;
} Piece;
typedef struct Block {
ubyte total;
Piece* pieces;
} Block;
Block *block_create(uint nb_pieces) {
Block *block = malloc(sizeof(Block) + (sizeof(Piece) * nb_pieces));
block->pieces = (Piece *) (&block + sizeof(Block));
return block;
}
我只是想知道这行代码:block->pieces = (&block + sizeof(Block));
是否总是 安全。我的意思是:我们可以确定 在 sizeof(Piece)
之后立即 ,我们将正好有 (sizeof(Piece) * nb_pieces)
吗?我们确定永远不会有对齐问题吗(即,如果它是 64 位对齐的,sizeof(Block)
的内存将小于 8 个字节,而 block->pieces
应该 而不是 正好指向 sizeof(Block)
,但是“sizeof(Block)
64 位对齐”)。
希望我说得够清楚了。
首先,这并没有达到您的预期,原因有很多:
block->pieces = (&block + sizeof(Block));
首先,&block
是变量block
的地址,不是block
的内容,类型是block **
。最多,您可以安全地将此指针值加 1,因为任何更多都会创建一个超过此变量末尾的指针,该指针无效。
那么您需要将 &block
更改为 block
。这仍然不会达到您的预期,因为指针算法将原始地址增加了对象大小的倍数。因此,向其中添加 sizeof(Block)
并不是向上移动 1 个数组元素,而是向上移动 sizeof(Block)
个数组元素。
要解决此问题,您需要 block + 1
。现在您需要开始担心对齐问题。为了使 Pieces
数组正确对齐,您需要检查 _Alignof(Block)
和 _Alignof(Piece)
是否相同。如果不是,则需要添加填充字节:
int padding = 0;
if (_Alignof(Block) % _Alignof(Piece) != 0) {
padding = _Alignof(Piece) - (_Alignof(Block) % _Alignof(Piece));
}
Block *block = malloc(sizeof(Block) + padding + (sizeof(Piece) * nb_pieces));
block->pieces = (Piece *)((char *)(block + 1) + padding);
当然,您可以通过将 pieces
成员设为 灵活数组成员来避免这一切 :
typedef struct Block {
ubyte total;
Piece pieces[];
} Block;
而且自己分配就够了。
我把整个代码放在了不同的文件中(.h
和 .c
文件)
typedef unsigned char ubyte;
typedef unsigned int uint;
#include<stdbool.h>
typedef struct Cube {
ubyte n;
ubyte e;
ubyte s;
ubyte w;
} Cube;
typedef struct Piece {
Cube c;
bool is_main;
char offset_n;
char offset_s;
} Piece;
typedef struct Block {
ubyte total;
Piece* pieces;
} Block;
Block *block_create(uint nb_pieces) {
Block *block = malloc(sizeof(Block) + (sizeof(Piece) * nb_pieces));
block->pieces = (Piece *) (&block + sizeof(Block));
return block;
}
我只是想知道这行代码:block->pieces = (&block + sizeof(Block));
是否总是 安全。我的意思是:我们可以确定 在 sizeof(Piece)
之后立即 ,我们将正好有 (sizeof(Piece) * nb_pieces)
吗?我们确定永远不会有对齐问题吗(即,如果它是 64 位对齐的,sizeof(Block)
的内存将小于 8 个字节,而 block->pieces
应该 而不是 正好指向 sizeof(Block)
,但是“sizeof(Block)
64 位对齐”)。
希望我说得够清楚了。
首先,这并没有达到您的预期,原因有很多:
block->pieces = (&block + sizeof(Block));
首先,&block
是变量block
的地址,不是block
的内容,类型是block **
。最多,您可以安全地将此指针值加 1,因为任何更多都会创建一个超过此变量末尾的指针,该指针无效。
那么您需要将 &block
更改为 block
。这仍然不会达到您的预期,因为指针算法将原始地址增加了对象大小的倍数。因此,向其中添加 sizeof(Block)
并不是向上移动 1 个数组元素,而是向上移动 sizeof(Block)
个数组元素。
要解决此问题,您需要 block + 1
。现在您需要开始担心对齐问题。为了使 Pieces
数组正确对齐,您需要检查 _Alignof(Block)
和 _Alignof(Piece)
是否相同。如果不是,则需要添加填充字节:
int padding = 0;
if (_Alignof(Block) % _Alignof(Piece) != 0) {
padding = _Alignof(Piece) - (_Alignof(Block) % _Alignof(Piece));
}
Block *block = malloc(sizeof(Block) + padding + (sizeof(Piece) * nb_pieces));
block->pieces = (Piece *)((char *)(block + 1) + padding);
当然,您可以通过将 pieces
成员设为 灵活数组成员来避免这一切 :
typedef struct Block {
ubyte total;
Piece pieces[];
} Block;
而且自己分配就够了。