C - 取消引用术语后的引用

C - Reference after dereference terminology

这个问题是关于术语的。

int main()
{
    unsigned char array[10] = {0};

    void *ptr = array;

    void *middle = &ptr[5]; // <== dereferencing ‘void *’ pointer
}

Gcc 发出警告 Dereferencing void pointer

我理解这个警告,因为编译器需要计算实际偏移量,但它不能,因为 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

解引用和存储访问有什么区别?

But I disagree with the error message. This is not a dereference. I can't find a dereference explanation where it is something else than taking value of something.

该标准未提供术语 "dereference" 的正式定义。它唯一使用它的地方是(非规范)footnote 102:

[...] Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the end of its lifetime.

但是请注意,本说明将取消引用描述为一元 * 运算符的行为,而不是对结果执行其他操作的效果。您可以将操作视为将指针转换为它所指向的对象,如果指针实际上不指向所指向类型的对象,或者如果指向to type 是一个不完整的类型,例如 void。即使生成的对象未被使用,这样的问题也正式存在。

现在我承认这里存在混淆的空间,因为在不使用结果对象的情况下执行取消引用是无用的,但这不是重点。考虑以下完整的 C 语句:

1 + 2;

你会否认它只是因为结果未被使用而执行加法吗?

现在,您的(子)表达式 ptr[5] 被定义为具有与 (*((ptr)+(5))) 相同的含义。指针加法表达式的类型与所涉及指针的类型相同,因此在将一元 * 运算符应用于该表达式的意义上,确实涉及取消引用 void *类型。

不过,尽管我认为错误信息是正确的,我同意这是一个糟糕的选择。这里有一个更基本的问题,也是按求值顺序最先出现的问题,它违反了语言约束,即在指针添加中,指针必须指向 complete 类型,void 不是。实际上,很难将发出的消息解释为满足约束违规导致诊断的要求。它似乎是关于一个不同的问题——一个产生未定义行为但不涉及约束违反的问题。

您还评论:

Same thing for offsetof:

#define offsetof(a,b) ((int)(&(((a*)(0))->b)))

[...] But this is not a null pointer dereference! Is it?

那里小心点。 C语言没有定义offsetof()宏替换文本的具体形式;您提供的是实施细节。

我们可以很容易地在这里转移到语义上,因为 "dereference" 不是标准中定义的术语,所以我将解决一个类似的问题:when the macro arguments meet the requirements offsetof() 宏的定义是否扩展为具有明确行为的表达式?

当间接成员选择运算符 (->) 的左侧操作数具有可接受的类型但未指向任何对象(例如当它为 null 时)时,该标准未定义行为。因此,该行为是未定义的。或者,如果我们将 a->b 完全等同于 ((*a).b),那么当 a 不指向任何对象时,行为是明确未定义的。无论哪种方式,C 语言都没有为表达式定义行为。

但这就是您的特定宏定义是实现细节变得很重要的地方。从中提取它的实现可以自由地提供它希望的任何行为,特别是,它可以提供可靠地满足 C 对 offsetof() 宏的规范的行为。你不应该自己依赖这样的代码。即使在提供该表单的 offsetof() 定义的实现中,您也不能确定它是否也使用了一些特殊的内部魔法——您自己的代码无法直接使用——来使其工作。