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()
定义的实现中,您也不能确定它是否也使用了一些特殊的内部魔法——您自己的代码无法直接使用——来使其工作。
这个问题是关于术语的。
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()
定义的实现中,您也不能确定它是否也使用了一些特殊的内部魔法——您自己的代码无法直接使用——来使其工作。