【发布时间】:2021-03-22 05:12:33
【问题描述】:
2020 年 12 月 11 日更新:感谢@“Some程序员老兄”在评论中提出的建议。
我的根本问题是我们的团队正在实现一个动态类型的存储引擎。我们分配了多个 16-aligned 的 char array[PAGE_SIZE] 缓冲区来存储动态类型的数据(没有固定的结构)。出于效率原因,我们无法执行字节编码或分配额外空间来使用memcpy。
既然已经确定了对齐方式(即16),剩下的就是使用指针的强制转换来访问指定类型的对象,例如:
int main() {
// simulate our 16-aligned malloc
_Alignas(16) char buf[4096];
// store some dynamic data:
*((unsigned long *) buf) = 0xff07;
*(((double *) buf) + 2) = 1.618;
}
但是我们的团队对这个操作是否是未定义的行为存在争议。
看过很多类似的问题,比如
- Why does -Wcast-align not warn about cast from char* to int* on x86?
- How to cast char array to int at non-aligned position?
- C undefined behavior. Strict aliasing rule, or incorrect alignment?
- SEI CERT C C.S EXP36-C
但是这些和我对C标准的理解不同,我想知道是不是我的误解。
主要的困惑是关于 C11 的 6.3.2.3 #7 部分:
指向对象类型的指针可以转换为指向不同对象类型的指针。如果 结果指针 未正确对齐 68) 引用类型,则行为未定义。
68) 通常,“正确对齐”的概念是可传递的:如果指向类型 A 的指针正确对齐指向类型 B 的指针,而指向类型 B 的指针又正确对齐指向类型 C 的指针,则指向类型 A 的指针与指向类型 C 的指针正确对齐。
这里的结果指针是指指针对象还是指针值?
在我看来,我认为答案是Pointer Object,但更多的答案似乎表明Pointer Value。
解释A:指针对象
我的想法如下:指针本身就是一个对象。根据6.2.5 #28,不同的指针可能有不同的表示和对齐要求。因此,根据6.3.2.3 #7,只要两个指针具有相同的对齐方式,就可以安全地转换它们而没有未定义的行为,但不能保证它们可以被取消引用。 在程序中表达这个想法:
#include <stdio.h>
int main() {
char buf[4096];
char *pc = buf;
if (_Alignof(char *) == _Alignof(int *)) {
// cast safely, because they have the same alignment requirement?
int *pi = (int *) pc;
printf("pi: %p\n", pi);
} else {
printf("char * and int * don't have the same alignment.\n");
}
}
解释B:指针值
但是,如果 C11 标准讨论的是引用类型的 指针值 而不是 指针对象。上面代码的对齐检查是没有意义的。 在程序中表达这个想法:
#include <stdio.h>
int main() {
char buf[4096];
char *pc = buf;
/*
* undefined behavior, because:
* align of char is 1
* align of int is 4
*
* and we don't know whether the `value` of pc is 4-aligned.
*/
int *pi = (int *) pc;
printf("pi: %p\n", pi);
}
哪种解释是正确的?
【问题讨论】:
-
所有普通指针大小相同。
sizeof(int*) == sizeof(char*)。因此,所有指针都将具有相同的对齐方式:_Alignof(int*) == _Alignof(char*),因为对齐方式取决于大小。 -
@MartinYork 不正确。数组的对齐方式是基本对象类型的对齐方式,因此
buf的对齐要求仅为 1,而与数组的大小无关。 -
@MartinYork “所有普通指针的大小都相同。” --> 对象指针和函数指针通常可以有不同的大小。各种对象指针的大小可能不同,但如今这种情况很少见。
-
@ryyker 我指的是 OP 引用的措辞。我曾经和dbush的回答类似,但是再看引用的部分我觉得不是很明显。
-
@RichardBryant:不。我指出检查是没有意义的(因为它总是正确的)。
char* x = 0x01;有效。int* y = (int*)x;无效。指针 x/y 具有相同的对齐方式,您可以分配给它们。但是分配非法值是UB。解释B是正确的。
标签: c pointers language-lawyer strict-aliasing