4

这个问题是关于术语的。

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

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

4

1 回答 1

2

但我不同意错误信息。这不是取消引用。我找不到一个取消引用的解释,它是什么,而不是取值。

该标准没有提供术语“取消引用”的正式定义。它使用它的唯一地方是(非规范)脚注 102

[...] 一元运算符取消引用指针的无效值包括*空指针、与指向的对象类型不适当对齐的地址以及对象在其生命周期结束后的地址。

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

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

1 + 2;

您是否会因为结果未使用而否认它执行加法?

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

尽管如此,虽然我认为错误信息是正确的,但我同意这是一个糟糕的选择。这里的一个更基本的问题,也是在评估顺序中首先遇到的问题,是违反语言约束,即在指针加法中,指针必须指向一个完整的类型,而事实void并非如此。实际上,很难将发出的消息解释为满足约束违反导致诊断的要求。这似乎是关于一个不同的问题——产生未定义的行为,但不涉及违反约束的问题。

你还说:

offsetof 也一样:

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

[...] 但这不是空指针取消引用!是吗?

小心点,那里。C语言没有定义offsetof()宏的替换文本的具体形式;你所展示的是一个实现细节。

我们在这里可以很容易地转移到语义上,因为“取消引用”在标准中不是一个定义的术语,所以我将解决一个类似的问题:当宏参数满足宏的要求时offsetof(),呈现的定义是否扩展为表达式具有明确定义的行为?

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

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

于 2017-11-02T20:44:49.807 回答