这个问题与术语有关。
int main()
{
unsigned char array[10] = {0};
void *ptr = array;
void *middle = &ptr[5]; // <== dereferencing ‘void *’ pointer
}
Gcc发出警告解除引用无效指针。
我理解这个警告是因为编译器需要计算实际的偏移量,因为void
没有标准尺寸,所以它不能。
但我不同意错误信息。这不是一个解除引用。我无法找到一个解除引用的解释,除了重视某些东西之外别无其他。
对于offsetof也是如此:
#define offsetof(a,b) ((int)(&(((a*)(0))->b)))
由于空指针取消引用,有很多关于这是否是UB的线程。但这不是空指针取消引用!是吗?
汇编代码中没有存储访问
mov rax, QWORD PTR [rbp-48]
add rax, 5
mov QWORD PTR [rbp-40], rax
取消引用和存储访问有什么区别?
答案 0 :(得分:2)
但我不同意错误信息。这不是一个解除引用。我找不到一个解除引用的解释,除了获取某些东西之外的其他东西。
该标准未提供“解除引用”一词的正式定义。它使用它的唯一地方是(非规范性的)footnote 102:
[...]由一元
*
运算符解除引用指针的无效值中有一个空指针,一个地址与指向的对象类型不一致,以及一个对象的地址。它的一生结束。
但请注意,本说明将解除引用表征为一元*
运算符的行为,而不是对结果执行某些其他操作的效果。您可以将操作视为将指针转换为它指向的对象,如果指针实际上没有指向指向类型的对象,或者如果键入是不完整的,如void
。即使生成的对象未被使用,这样的问题也会正式存在。
现在我承认,由于在不使用生成的对象的情况下执行取消引用是无用的,因此存在混淆的空间,但这不是重点。请考虑以下完整的C语句:
1 + 2;
您是否会因为结果未使用而拒绝执行添加?
现在,您的(子)表达式ptr[5]
被定义为具有与(*((ptr)+(5)))
相同的含义。指针添加表达式的类型与所涉及的指针的类型相同,因此确实涉及解除引用void *
,在将一元*
运算符应用于该表达式的意义上类型。
尽管如此,虽然我认为错误信息是正确的,我确实认为这是一个糟糕的选择。这里的一个更基本的问题,以及在评估顺序中首先达到的问题是违反语言约束,在指针添加中,指针必须指向完整类型,void
不是。实际上,很难将解释的消息解释为满足约束违规导致诊断的要求。它似乎是一个不同的问题 - 一个产生未定义的行为,但不涉及约束违规。
你也说:
对于offsetof也是如此:
#define offsetof(a,b) ((int)(&(((a*)(0))->b)))
[...]但这不是空指针解除引用!是吗?
小心,那里。 C语言没有定义offsetof()
宏的替换文本的特定形式;你提出的是一个实现细节。
我们可以很容易地转向语义,因为“dereference”不是标准中定义的术语,所以我将解决类似的问题:当宏参数满足{{1}的要求时宏,所提出的定义是否扩展为具有明确定义的行为的表达式?
当标准的左侧操作数具有可接受的类型但未指向任何对象(例如,当它为null时)时,标准不定义间接成员选择运算符(offsetof()
)的行为。因此行为未定义。或者,如果我们将->
完全等同于a->b
,那么当((*a).b)
未指向任何对象时,行为将明确未定义。无论哪种方式,C语言都没有定义表达式的行为。
但是,这是您的特定宏定义是实现细节变得非常重要的地方。绘制它的实现可以自由地提供它所希望的任何行为,特别是,它可以提供可靠地满足C a
宏规范的行为。你不应该自己依赖这些代码。即使在提供该offsetof()
形式的offsetof()
定义的实现中,您也无法确定它是否也使用了一些特殊的内部魔法 - 不能直接用于您自己的代码 - 以使其有效。