你能证明当你取消引用一个 void 指针时为什么强制转换很重要吗?
Can you proof why casting is important when I deference a void pointer?
当我取消引用 void 指针时,为什么需要进行转换?
我有这个例子:
int x;
void* px = &x;
*px = 9;
你能证明为什么这行不通吗?
根据定义,void
指针指向一个我不确定是什么类型的对象。
根据定义,当你使用一元*
运算符访问指针指向的对象时,你必须知道(嗯,编译器必须知道)对象的类型是什么。
所以我们刚刚证明了我们不能直接使用*
解引用一个void
指针;我们必须首先明确地将 void
指针转换为某个实际的对象指针类型。
现在,在很多人的心目中,"obvious" 对 "what type does/should a 'generic' pointer point to?" 的回答是“char
”。而且,曾几何时,在发明 void
类型之前,字符指针通常用作 "generic" 指针。所以一些编译器(包括,特别是 gcc)稍微扩展了一些东西,让你可以用 void
指针做比标准要求更多的事情(比如指针算术)。
因此 可能 解释您问题中的代码如何能够 "work"。 (但是,在您的情况下,由于指向的类型实际上是 int
,而不是 char
,如果它是 "worked",那只是因为您使用的是小端机器。)
...话虽如此,我发现您问题中的代码 不 对我有用,甚至在 gcc 下也不行。它首先给我一个非致命警告:
warning: dereferencing ‘void *’ pointer
但后来它改变了主意,认为这是一个错误:
error: invalid use of void expression
我试过的第二个编译器说了类似的话:
error: incomplete type 'void' is not assignable
附录:多说一点 为什么 当您取消引用指针时需要指向的类型:
当您使用 *
访问指针时,编译器将发出代码以从(或可能存储到)指向的位置获取。但是编译器将不得不发出访问一定数量字节的代码,并且在许多情况下,如何解释这些字节可能很重要。字节的数量和解释都由类型决定(这就是 for 的类型),这正是需要实际的非 void
类型的原因。
据我所知,理解此要求的最佳方法之一是考虑像
这样的代码
*p + 1
或者,甚至更好
*p += 1
如果 p
指向 char
,编译器可能会发出某种 addb
("add byte") 指令。
如果 p
指向 int
,编译器将发出一条普通的 add
指令。
如果 p
指向 float
或 double
,编译器将发出一条浮点加法指令。等等。
但是如果p
是一个void *
,编译器不知道该做什么。它抱怨(以错误消息的形式)不仅仅是因为 C 标准说你不能取消引用 void
指针,更重要的是,因为编译器根本不知道如何处理你的代码。
更具体地说,取消引用 void 指针违反了 6.5.3.2 Address and indirection operators, paragraph 4 的措辞:
The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ''pointer to type'', the result has type ''type''. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.
因为指向 void 的指针没有 "type" - 它不能被取消引用。请注意,这超出了未定义的行为 - 它违反了 C 语言标准。
简而言之:
赋值表达式的目标必须是可修改的左值,不能是void
表达式。这是因为 void
类型不表示 any 值 - 它表示 不存在 值。您不能创建 void
类型的对象。
如果表达式 px
的类型为 void *
,则表达式 *px
的类型为 void
。尝试分配给 *px
是 约束违规 并且编译器需要为此大喊大叫。
如果您想通过 px
为 x
分配一个新值,那么您 必须 将 px
转换为 int *
在取消引用之前:
*((int *)px) = 5;
6.2.5 Types
...
19 The void
type comprises an empty set of values; it is an incomplete object type that
cannot be completed.
...
6.3.2.1 Lvalues, arrays, and function designators
1 An lvalue is an expression (with an object type other than void
) that potentially
designates an object;64) if an lvalue does not designate an object when it is evaluated, the
behavior is undefined. When an object is said to have a particular type, the type is
specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that
does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including,
recursively, any member or element of all contained aggregates or unions) with a const-qualified type.
...
6.3.2.2 void
1 The (nonexistent) value of a void
expression (an expression that has type void
) shall not
be used in any way, and implicit or explicit conversions (except to void
) shall not be
applied to such an expression. If an expression of any other type is evaluated as a void
expression, its value or designator is discarded. (A void expression is evaluated for its
side effects.)
...
6.3.2.3 Pointers
1 A pointer to void
may be converted to or from a pointer to any object type. A pointer to
any object type may be converted to a pointer to void and back again; the result shall
compare equal to the original pointer.
...
6.5.3.2 Address and indirection operators
...
4 The unary <strong>*</strong>
operator denotes indirection. If the operand points to a function, the result is
a function designator; if it points to an object, the result is an lvalue designating the
object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an
invalid value has been assigned to the pointer, the behavior of the unary <strong>*</strong>
operator is
undefined.102)
...
6.5.16 Assignment operators
...
Constraints
2 An assignment operator shall have a modifiable lvalue as its left operand.
它可能不起作用,因为它违反了 ISO C 标准中需要诊断的规则,并且(我猜)您的编译器将其视为致命情况。
根据 ISO C99 以及 C11 草案 (n1548),使用 *
解引用运算符的唯一限制是“[t] 一元 *
的操作数运算符应具有指针类型。” [6.5.3.2¶2, n1548] 我们这里的代码满足该约束,并且没有语法错误。因此,使用 *
运算符不需要诊断。
但是,应用于 void *
指针的 *
运算符的含义是什么?
"The unary *
operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. [6.5.3.2¶4, n1548]
类型void
既不是函数也不是对象类型,所以中间那句说产生函数或对象指示符的语句不适用于我们的情况。上面引用的最后一句话是适用的;它要求取消引用 void *
的表达式具有 void
类型。
因此 *px = 9;
搁浅了,因为它将 int
值分配给 void
表达式。赋值需要对象类型的左值表达式; void
不是对象类型,表达式肯定不是左值。约束的确切措辞是:"An assignment operator shall have a modifiable lvalue as its left operand." [6.5.16¶2, n1548] 违反此约束需要进行诊断。
从我对标准的幼稚阅读看来,表达式 *px
as such 是有效的;只是不必尝试从中提取结果,或将其用作赋值的目标。如果这是真的,它可以用作表达式语句,其值被丢弃:if (foo()) { *px; }
,并且它也可以冗余地转换为 void
:(void) *px
。这些看似毫无意义的情况可能会以某种方式被某些类型的宏利用,或者至少出现在某些类型的宏中。
例如,如果我们想确定某个宏的参数是一个指针,我们可以利用 *
需要指针操作数的约束:
#define MAC(NUM, PTR) ( ... (void) *(PTR) ...)
即在宏的某处,我们取消引用指针并丢弃结果,这将诊断 PTR
是否不是指针。看起来 ISO C 允许这种用法,即使 PTR
是 void *
,这可以说是有用的。
当我取消引用 void 指针时,为什么需要进行转换?
我有这个例子:
int x;
void* px = &x;
*px = 9;
你能证明为什么这行不通吗?
根据定义,void
指针指向一个我不确定是什么类型的对象。
根据定义,当你使用一元*
运算符访问指针指向的对象时,你必须知道(嗯,编译器必须知道)对象的类型是什么。
所以我们刚刚证明了我们不能直接使用*
解引用一个void
指针;我们必须首先明确地将 void
指针转换为某个实际的对象指针类型。
现在,在很多人的心目中,"obvious" 对 "what type does/should a 'generic' pointer point to?" 的回答是“char
”。而且,曾几何时,在发明 void
类型之前,字符指针通常用作 "generic" 指针。所以一些编译器(包括,特别是 gcc)稍微扩展了一些东西,让你可以用 void
指针做比标准要求更多的事情(比如指针算术)。
因此 可能 解释您问题中的代码如何能够 "work"。 (但是,在您的情况下,由于指向的类型实际上是 int
,而不是 char
,如果它是 "worked",那只是因为您使用的是小端机器。)
...话虽如此,我发现您问题中的代码 不 对我有用,甚至在 gcc 下也不行。它首先给我一个非致命警告:
warning: dereferencing ‘void *’ pointer
但后来它改变了主意,认为这是一个错误:
error: invalid use of void expression
我试过的第二个编译器说了类似的话:
error: incomplete type 'void' is not assignable
附录:多说一点 为什么 当您取消引用指针时需要指向的类型:
当您使用 *
访问指针时,编译器将发出代码以从(或可能存储到)指向的位置获取。但是编译器将不得不发出访问一定数量字节的代码,并且在许多情况下,如何解释这些字节可能很重要。字节的数量和解释都由类型决定(这就是 for 的类型),这正是需要实际的非 void
类型的原因。
据我所知,理解此要求的最佳方法之一是考虑像
这样的代码*p + 1
或者,甚至更好
*p += 1
如果 p
指向 char
,编译器可能会发出某种 addb
("add byte") 指令。
如果 p
指向 int
,编译器将发出一条普通的 add
指令。
如果 p
指向 float
或 double
,编译器将发出一条浮点加法指令。等等。
但是如果p
是一个void *
,编译器不知道该做什么。它抱怨(以错误消息的形式)不仅仅是因为 C 标准说你不能取消引用 void
指针,更重要的是,因为编译器根本不知道如何处理你的代码。
更具体地说,取消引用 void 指针违反了 6.5.3.2 Address and indirection operators, paragraph 4 的措辞:
The unary * operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ''pointer to type'', the result has type ''type''. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.
因为指向 void 的指针没有 "type" - 它不能被取消引用。请注意,这超出了未定义的行为 - 它违反了 C 语言标准。
简而言之:
赋值表达式的目标必须是可修改的左值,不能是void
表达式。这是因为 void
类型不表示 any 值 - 它表示 不存在 值。您不能创建 void
类型的对象。
如果表达式 px
的类型为 void *
,则表达式 *px
的类型为 void
。尝试分配给 *px
是 约束违规 并且编译器需要为此大喊大叫。
如果您想通过 px
为 x
分配一个新值,那么您 必须 将 px
转换为 int *
在取消引用之前:
*((int *)px) = 5;
6.2.5 Types
...
19 Thevoid
type comprises an empty set of values; it is an incomplete object type that cannot be completed.
...
6.3.2.1 Lvalues, arrays, and function designators
1 An lvalue is an expression (with an object type other thanvoid
) that potentially designates an object;64) if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.
...
6.3.2.2 void
1 The (nonexistent) value of avoid
expression (an expression that has typevoid
) shall not be used in any way, and implicit or explicit conversions (except tovoid
) shall not be applied to such an expression. If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (A void expression is evaluated for its side effects.)
...
6.3.2.3 Pointers
1 A pointer tovoid
may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
...
6.5.3.2 Address and indirection operators
...
4 The unary<strong>*</strong>
operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an invalid value has been assigned to the pointer, the behavior of the unary<strong>*</strong>
operator is undefined.102)
...
6.5.16 Assignment operators
...
Constraints
2 An assignment operator shall have a modifiable lvalue as its left operand.
它可能不起作用,因为它违反了 ISO C 标准中需要诊断的规则,并且(我猜)您的编译器将其视为致命情况。
根据 ISO C99 以及 C11 草案 (n1548),使用 *
解引用运算符的唯一限制是“[t] 一元 *
的操作数运算符应具有指针类型。” [6.5.3.2¶2, n1548] 我们这里的代码满足该约束,并且没有语法错误。因此,使用 *
运算符不需要诊断。
但是,应用于 void *
指针的 *
运算符的含义是什么?
"The unary
*
operator denotes indirection. If the operand points to a function, the result is a function designator; if it points to an object, the result is an lvalue designating the object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. [6.5.3.2¶4, n1548]
类型void
既不是函数也不是对象类型,所以中间那句说产生函数或对象指示符的语句不适用于我们的情况。上面引用的最后一句话是适用的;它要求取消引用 void *
的表达式具有 void
类型。
因此 *px = 9;
搁浅了,因为它将 int
值分配给 void
表达式。赋值需要对象类型的左值表达式; void
不是对象类型,表达式肯定不是左值。约束的确切措辞是:"An assignment operator shall have a modifiable lvalue as its left operand." [6.5.16¶2, n1548] 违反此约束需要进行诊断。
从我对标准的幼稚阅读看来,表达式 *px
as such 是有效的;只是不必尝试从中提取结果,或将其用作赋值的目标。如果这是真的,它可以用作表达式语句,其值被丢弃:if (foo()) { *px; }
,并且它也可以冗余地转换为 void
:(void) *px
。这些看似毫无意义的情况可能会以某种方式被某些类型的宏利用,或者至少出现在某些类型的宏中。
例如,如果我们想确定某个宏的参数是一个指针,我们可以利用 *
需要指针操作数的约束:
#define MAC(NUM, PTR) ( ... (void) *(PTR) ...)
即在宏的某处,我们取消引用指针并丢弃结果,这将诊断 PTR
是否不是指针。看起来 ISO C 允许这种用法,即使 PTR
是 void *
,这可以说是有用的。