C中的void类型
Posted
技术标签:
【中文标题】C中的void类型【英文标题】:The void type in C 【发布时间】:2019-05-26 23:57:04 【问题描述】:C 中的void
类型在各种不同的情况下似乎很奇怪。有时它的行为类似于普通对象类型,例如 int
或 char
,有时它只是毫无意义(应该如此)。
看看我的sn-p。首先,您可以声明 void
对象似乎很奇怪,这意味着您什么都不声明。
然后我创建了一个int
变量并将其结果转换为void
,将其丢弃:
如果任何其他类型的表达式被评估为 void 表达式,其值或指示符被丢弃。 (ISO/IEC 9899:201x,6.3.2.2 无效)
我尝试使用 void
强制转换调用我的函数,但我的编译器给了我 (Clang 10.0):
error: too many arguments to function call, expected 0, have 1
所以原型中的void
表示nothing,而不是void
类型。
然后,我创建了一个指向void
的指针,取消引用它,并将“result”分配给我的int
变量。我收到“不兼容的类型”错误。 这意味着这里确实存在void
类型。
extern void a; // Why is this authorised ???
void foo(void); // This function takes no argument. Not the 'void' type.
int main(void)
int a = 42;
void *p;
// Expression result casted to 'void' which discards it (per the C standard).
(void)a;
// Casting to 'void' should make the argument inexistant too...
foo((void)a);
// Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
a = *p;
// Am I not passing the 'void' type ?
foo(*p);
return 0;
void
是实际类型,还是关键字 to 没有任何意义?因为有时它的行为类似于“此处不允许任何内容”指令,有时则类似于实际类型。
编辑:此问题不重复。这纯粹是关于 void
类型的语义。我不想解释如何使用void
、指向void
的指针或任何其他东西。我想要一个符合 C 标准的答案。
【问题讨论】:
可以将转换为 void 的变量传递给函数,然后在函数中转换回原始数据类型,例如void* thread_func((void)args)
,然后在 thread_func 函数中用作 (struct args)args
@Auxilus 我对此表示怀疑。将void
传递给不兼容类型的参数struct args
时会出错。
不一样的情况。我说的是void
类型,一个不能完成的不完整类型,它和完整类型“指向void
”的指针不是一回事。
Is void a data type in C?的可能重复
void
是实际类型;但是(void)
的函数参数列表意味着该函数不带参数;并不是说它需要一个void
类型的参数。 (void
类型的参数是不允许的)
【参考方案1】:
在 C 语言中引入了 void
类型,其含义比“null”或“nothing”更重要,用于不同的范围。
void
关键字可以引用 void type
、reference to void
、void expression
、void operand
或 void function
。它还明确定义了一个没有参数的函数。
让我们来看看其中的一些。
void
类型
首先,void
对象存在并具有一些特殊属性,如 ISO/IEC 9899:2017, §6.2.5 类型所述:
void 类型包含一组空值;它是一个不完整的对象类型,无法完成。
指针
更有用的reference to void
,或void *
,是对不完整类型的引用,但本身定义良好,然后是完整类型,有大小,可以用作任何其他标准变量ISO/IEC 9899:2017, §6.2.5 类型:
指向 void 的指针应具有与指向字符类型的指针相同的表示和对齐要求。
同样,指向兼容类型的合格或不合格版本的指针应具有相同的表示和对齐要求。
所有指向结构类型的指针都应具有相同的表示和对齐要求。
所有指向联合类型的指针都应具有相同的表示和对齐要求。
指向其他类型的指针不必具有相同的表示或对齐要求。
投射到void
它可以用作cast来使表达式无效,但允许完成该表达式的任何副作用。此概念在 ISO/IEC 9899:2017, §6.3 Conversions, §6.3.2.2 void 的标准中进行了解释:
不应以任何方式使用 void 表达式(具有 void 类型的表达式)的(不存在的)值,并且不应将隐式或显式转换(除了 void)应用于此类表达式。
如果任何其他类型的表达式被评估为 void 表达式,则其值或指示符将被丢弃。 (评估 void 表达式的副作用。)
转换为void
的一个实际示例是它用于防止对函数定义中未使用的参数发出警告:
int fn(int a, int b)
(void)b; //This will flag the parameter b as used
... //Your code is here
return 0;
上面的 sn-p 显示了用于静音编译器警告的标准做法。将参数 b
转换为 void
是一个有效的表达式,它不会生成代码并将 b
标记为用于防止编译器抱怨。
void
函数
标准的 §6.3.2.2 void 段落还涵盖了一些关于 void
函数的解释,这些函数不返回任何可在表达式中使用的值,但函数是无论如何调用以实现副作用。
void
指针属性
正如我们之前所说,指向void
的指针更有用,因为它们允许以通用方式处理对象引用,因为它们的属性在 ISO/IEC 9899:2017, §6.3.2.3 指针:
指向 void 的指针可以转换为指向任何对象类型的指针或从指向任何对象类型的指针转换。
指向任何对象类型的指针都可以转换为指向 void 的指针,然后再返回;结果应与原始指针比较等于。
作为实际示例,想象一个函数根据输入参数返回指向不同对象的指针:
enum
FAMILY, //Software family as integer
VERSION, //Software version as float
NAME //Software release name as char string
eRelease;
void *GetSoftwareInfo(eRelease par)
static const int iFamily = 1;
static const float fVersion = 2.0;
static const *char szName = "Rel2 Toaster";
switch(par)
case FAMILY:
return &iFamily;
case VERSION:
return &fVersion;
case NAME:
return szName;
return NULL;
在这个 sn-p 中,您可以返回一个通用指针,该指针可以依赖于输入 par
值。
void
作为函数参数
在所谓的 ANSI 标准之后引入了函数定义中 void
参数的使用,以有效区分具有可变数量参数的函数与具有无参数的函数。
来自标准ISO/IEC 9899:2017, 6.7.6.3 函数声明符(包括原型):
void
类型的未命名参数作为列表中唯一项的特殊情况指定该函数没有参数。
实际的编译器仍然支持带有空括号的函数声明以实现向后兼容性,但这是一个过时的功能,最终将在未来的标准版本中删除。请参阅未来方向 - §6.11.6 函数声明符:
使用带空括号的函数声明符(不是原型格式参数类型声明符)已过时 功能。
考虑以下示例:
int foo(); //prototype of variable arguments function (backward compatibility)
int bar(void); //prototype of no arguments function
int a = foo(2); //Allowed
int b = foo(); //Allowed
int c = bar(); //Allowed
int d = bar(1); //Error!
现在类似于您的测试,如果我们如下调用函数bar
:
int a = 1;
bar((void)a);
触发错误,因为将对象转换为 void
不会使其为空。因此,您仍在尝试将 void
对象作为参数传递给没有任何参数的函数。
副作用
根据要求,这是对副作用概念的简短说明。
副作用是由于执行语句而产生的任何对象和值的更改,并且不是直接的预期效果。
int a = 0;
(void)b = ++a;
在sn-p上面的void表达式失去直接作用,赋值b
,但是作为副作用增加a
的值。
标准中唯一的参考,解释含义,可以在5.1.2.3 程序执行中找到:
访问 volatile 对象、修改对象、修改 文件,或调用执行任何这些操作的函数都是 副作用,即执行状态的变化 环境。
表达式的评估通常包括两个值 计算和引发副作用。
【讨论】:
感谢您的回答。另外,当标准说评估空表达式的副作用时,它是什么意思?【参考方案2】:void
是一种类型。根据 C 2018 6.2.5 19,该类型没有值(它可以表示的值集为空)、不完整(其大小未知)、无法完成(其大小未知)。
关于extern void a;
,这并没有定义一个对象。它声明了一个标识符。如果在表达式中使用了a
(作为sizeof
或_Alignof
运算符的一部分除外),则必须在程序中的某处对其进行定义。由于在严格符合 C 语言中不能定义 void
对象,因此不能在表达式中使用 a
。所以我认为这个声明在严格符合 C 的情况下是允许的,但没有用。它可能在 C 实现中用作扩展,允许获取类型未知的对象的地址。 (例如,在一个模块中定义一个实际对象a
,然后在另一个模块中将其声明为extern void a;
,并在那里使用&a
获取它的地址。)
以(void)
作为参数列表的函数声明是一个杂项。理想情况下,()
可用于指示函数不带参数,就像 C++ 中的情况一样。然而,由于 C 的历史,()
被用来表示未指定的参数列表,因此必须发明其他东西来表示没有参数。所以(void)
被采用了。因此,(void)
是规则的一个例外,即 (int)
是用于采用 int
的函数,(double)
是用于采用双精度的函数,依此类推——(void)
是一种特殊情况函数不带参数,而不是带void
。
在foo((void) a)
中,转换不会使值“不存在”。它将a
转换为void
类型。结果是void
类型的表达式。该表达式“存在”,但它没有值且不能在表达式中使用,因此在 foo((void) a)
中使用它会导致错误消息。
【讨论】:
【参考方案3】:来自 C 标准#6.2.5p19:
19 void 类型包含一组空值;它是无法完成的不完整对象类型。
这表明void
类型存在。
疑问一:
void foo(void); // This function takes no argument. Not the 'void' type.
正确。 来自 C Standard#6.7.6.3p10 [强调我的]:
10 void 类型的未命名参数作为列表中唯一项的特殊情况指定函数没有参数。
这是他们必须添加到语言语法中的一个特殊情况,因为void foo();
已经意味着不同的东西(void foo();
没有指定任何关于foo
的参数的内容)。如果不是 void foo();
的旧含义,void foo();
将是声明无参数函数的语法。您无法从中概括任何内容。这只是一个特例。
疑问二:
// Casting to 'void' should make the argument inexistant too...
foo((void)a);
不会,因为void
虽然不完整,但也是一个对象类型。
疑点三:
// Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
a = *p;
是的,它确实存在,因此编译器在此语句上报告错误。
疑问4:
// Am I not passing the 'void' type ?
foo(*p);
foo()
函数声明:
void foo(void);
^^^^
参数列表中的void
表示该函数不会接受任何参数,因为它已被声明为没有参数。
仅供参考,请参阅 C Standard#5.1.2.2.1p1 [emphasis mine]:
1 程序启动时调用的函数名为main。实现没有声明这个函数的原型。它应定义为返回类型为 int 且无参数:
int main(void) /* ... */ ^^^^
疑问5:
extern void a; // Why is this authorised ???
这是被授权的,因为void
是一个有效的类型并且它只是一个声明。没有存储空间将分配给a
。
【讨论】:
【参考方案4】:在 C 中,void
不能被视为 数据类型,它是一个关键字,用作代替数据类型的占位符,以表明实际上没有数据。所以这个
void a;
无效。
在这里
void foo(void);
void
关键字用于通知编译器foo
不会接受任何输入参数,也没有返回类型。
以下情况
int a = 42;
void *p;
a = *p; /* this causes error */
a = *p;
是错误的,因为您不能直接取消引用 void 指针,您需要先执行正确的类型转换。例如
a = *(int*)p; /* first typecast and then do dereference */
还有这个
foo(*p);
错误有两个原因,
首先foo()
不需要任何参数。
其次你不能做*p
因为p
是空指针。如果foo()
声明为void foo(int);
,则正确的是foo(*(int*)p);
。
注意这个
(void)a;
什么都不做,所以你的编译器可能不会给出任何警告,但当你喜欢时
int b = (void)a;
编译器不允许 void
不被视为数据类型。
终于来了
extern void a; // Why is this authorised ???
这只是一个声明而不是定义,a
在你定义它之前不存在,因为a
有 extern
存储类,你需要在某个地方定义&当你要定义时
a = 10;
编译器抛出错误
错误:“a”的类型不完整
来自 C 标准 6.2.5 类型
void
类型包含一个empty set of values
;它是一个 无法完成的不完整对象类型。
6.3.2.2 无效
void
表达式的(不存在的)值(具有 type void) 不得以任何方式使用,无论是隐式还是显式 转换(无效除外)不适用于此类 表达。如果任何其他类型的表达式被评估为void
表达式,其值或指示符被丢弃。 (避免 表达式的副作用被评估。)
6.3.2.3 指针
指向 void 的指针可以转换为或从 指向任何 对象类型。指向任何对象类型的指针都可以转换为 指向 void 并再次返回的指针;结果应比较等于 原始指针。
storage-class specifier
或类型限定符修改关键字void
作为函数参数类型列表(6.7.6.3)。尝试使用
void
表达式的值,或 隐式或显式转换(void 除外)应用于 无效表达式(6.3.2.2)。
【讨论】:
其实我读了标准,这样你就可以取消引用 void 指针但你不能使用值... 除了在a = *p
表达式中,错误不是来自无效的void
指针解引用,而是来自不兼容的类型赋值,这意味着void
类型被编译器。
@Prion 在a = *p;
中唯一错误的是*p
,因为您没有进行类型转换以正确类型或编译器不知道p
的类型是什么,因为它无法识别@ 987654357@ 类型。正确的版本是a = *(int*)p;
,因为p
的类型转换为int*
& 编译器知道p
的类型,尽管它暂时进行了类型转换。
@AnttiHaapala 是对的,你可以直接取消引用指向void
的指针,你不能使用它的值,但是取消引用是被授权的。
“void 不能被视为数据类型” 标准将其称为类型:"C11 6.2.5 "Types" 19 — @987654363 @type 包含一组空值;它是一个无法完成的不完整对象类型。”【参考方案5】:
首先,你可以声明一个 void 对象似乎很奇怪,这意味着你什么都不声明。
void
是无法完成的不完整对象类型。这主要定义了它在常规上下文中的用途,即不为void
提供特殊处理的上下文。您的 extern
声明就是这样的常规上下文之一。在非定义声明中使用不完整的数据类型是可以的。
但是,您将永远无法为该声明提供匹配的定义。
所以原型中的 void 没有任何意义,也不是 void 类型。
正确。参数必须是未命名的。并且(void)
组合被给予特殊处理:它不是void
类型的一个参数,而是根本没有参数。
然后,我创建了一个指向 void 的指针,取消引用它,并将“结果”分配给我的 int 变量。我收到“不兼容的类型”错误。这意味着这里确实存在 void 类型。
没有。将一元 *
运算符应用于 void *
指针是非法的。由于这个原因,您的代码已经无效。您的编译器发出了误导诊断消息。正式地,诊断消息不需要正确描述问题的根源。编译器可能只是说“嗨!”。
void 是一个实际的类型,还是一个没有任何意义的关键字?
它是一种类型。它是一种不完整的对象类型,无法完成。
【讨论】:
以上是关于C中的void类型的主要内容,如果未能解决你的问题,请参考以下文章
UB 是不是将 void 指针与 C 中的类型化指针进行比较(是不是相等)?