我是否正确应用了严格别名规则?
Have I applied the rule of strict aliasing correctly?
我当前的(简化的)缓冲区 API 看起来像这样:
typedef struct {
size_t offset;
size_t size;
uint8_t *data;
} my_buffer;
// Writes an unsigned int 8 to the buffer
bool my_buffer_write_u8(my_buffer *buffer, uint8_t value) {
if (buffer->offset >= buffer->size) return false;
buffer->data[buffer->offset] = value;
++buffer->offset;
return true;
}
但是,在刷新了我对 C 中严格别名规则的了解之后,我对这个用例不太确定:
char string[32];
my_buffer buffer;
buffer.size = sizeof(string);
buffer.data = string; // <-- I think this violates the strict aliasing rule
buffer.offset = 0;
// the function calls access buffer.data which is defined to be `uint8_t *` and not `char *`
// in other words, I'm manipulating a `char *` through a `uint8_t *`:
// even though uint8_t is almost always unsigned char, it is nevertheless not the same as unsigned char
my_buffer_write_u8(&buffer, 'h');
my_buffer_write_u8(&buffer, 'e');
my_buffer_write_u8(&buffer, 'l');
my_buffer_write_u8(&buffer, 'l');
my_buffer_write_u8(&buffer, 'o');
my_buffer_write_u8(&buffer, '[=11=]');
我认为我应该在缓冲区结构中使用 void *
并使用 (char *)
转换来访问基础数据:
typedef struct {
size_t offset;
size_t size;
void *data;
} my_buffer;
// Writes an unsigned int 8 to the buffer
bool my_buffer_write_u8(my_buffer *buffer, uint8_t value) {
if (buffer->offset >= buffer->size) return false;
unsigned char *data = (unsigned char *)buffer->data;
data[buffer->offset] = value;
++buffer->offset;
return true;
}
因为 char *
、unsigned char *
和 signed char *
总是假设为其他数据类型的别名。
uint8_t *
(按照标准)
就不能这么说了
如果 CHAR_BIT
是 8
那么这个带有 (void *)
的调整后的代码应该和 uint8_t
版本完全一样。
现在问题来了:我是否正确应用了严格别名规则?
如果 uint8_t
与 unsigned char
不同,则为 UB。假设 uint8_t
存在,则可能性很小,因为
- CHAR_BIT 至少为 8,参见 http://port70.net/~nsz/c/c11/n1570.html#5.2.4.2.1p1
- unsigned char 没有填充位
- unsigned char 是最小的可寻址单元
但是,标准并没有明确要求uint8_t
与unsigned char
是同一类型。因此,它是由实现定义的。
考虑应用以下线程中的解决方案来检查上述类型是否相同。
最好使用char*
/unsigned char*
来访问数据。但是,如果代码的重构很麻烦,那么只需添加检查类型 uint8_t
和 unsigned char
是否相同,如果不相同则拒绝编译。
我当前的(简化的)缓冲区 API 看起来像这样:
typedef struct {
size_t offset;
size_t size;
uint8_t *data;
} my_buffer;
// Writes an unsigned int 8 to the buffer
bool my_buffer_write_u8(my_buffer *buffer, uint8_t value) {
if (buffer->offset >= buffer->size) return false;
buffer->data[buffer->offset] = value;
++buffer->offset;
return true;
}
但是,在刷新了我对 C 中严格别名规则的了解之后,我对这个用例不太确定:
char string[32];
my_buffer buffer;
buffer.size = sizeof(string);
buffer.data = string; // <-- I think this violates the strict aliasing rule
buffer.offset = 0;
// the function calls access buffer.data which is defined to be `uint8_t *` and not `char *`
// in other words, I'm manipulating a `char *` through a `uint8_t *`:
// even though uint8_t is almost always unsigned char, it is nevertheless not the same as unsigned char
my_buffer_write_u8(&buffer, 'h');
my_buffer_write_u8(&buffer, 'e');
my_buffer_write_u8(&buffer, 'l');
my_buffer_write_u8(&buffer, 'l');
my_buffer_write_u8(&buffer, 'o');
my_buffer_write_u8(&buffer, '[=11=]');
我认为我应该在缓冲区结构中使用 void *
并使用 (char *)
转换来访问基础数据:
typedef struct {
size_t offset;
size_t size;
void *data;
} my_buffer;
// Writes an unsigned int 8 to the buffer
bool my_buffer_write_u8(my_buffer *buffer, uint8_t value) {
if (buffer->offset >= buffer->size) return false;
unsigned char *data = (unsigned char *)buffer->data;
data[buffer->offset] = value;
++buffer->offset;
return true;
}
因为 char *
、unsigned char *
和 signed char *
总是假设为其他数据类型的别名。
uint8_t *
(按照标准)
如果 CHAR_BIT
是 8
那么这个带有 (void *)
的调整后的代码应该和 uint8_t
版本完全一样。
现在问题来了:我是否正确应用了严格别名规则?
如果 uint8_t
与 unsigned char
不同,则为 UB。假设 uint8_t
存在,则可能性很小,因为
- CHAR_BIT 至少为 8,参见 http://port70.net/~nsz/c/c11/n1570.html#5.2.4.2.1p1
- unsigned char 没有填充位
- unsigned char 是最小的可寻址单元
但是,标准并没有明确要求uint8_t
与unsigned char
是同一类型。因此,它是由实现定义的。
考虑应用以下线程中的解决方案来检查上述类型是否相同。
最好使用char*
/unsigned char*
来访问数据。但是,如果代码的重构很麻烦,那么只需添加检查类型 uint8_t
和 unsigned char
是否相同,如果不相同则拒绝编译。