C语言中的void类型

7
在不同的情况下,C语言中的void类型似乎有些奇怪。有时它的行为类似于普通的对象类型,例如intchar,而有时它什么也不表示(正如它应该做的那样)。
看看我的片段。首先,你可以声明一个void对象,这意味着你什么也没有声明,这似乎很奇怪。
然后我创建了一个int变量,并将其结果转换为void,将其丢弃:
如果对任何其他类型的表达式进行评估作为void表达式,则其值或指示符被丢弃。(ISO/IEC 9899:201x, 6.3.2.2 void)
我试图使用void强制转换调用我的函数,但我的编译器给了我一个错误(Clang 10.0):
error: too many arguments to function call, expected 0, have 1
因此原型中的void表示无意义,而不是类型void
但随后,我创建了一个指向void的指针,对其进行了解引用,并将“result”分配给我的int变量。我得到了“incompatible type”错误。这意味着在这里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”是一种实际的类型还是一种意味着“没有”的关键字?因为有时它的行为就像指令“这里不允许任何东西”,而有时则像一种实际类型。
编辑:这个问题并不是重复。它纯粹是关于“void”类型的语义。我不想要任何关于如何使用“void”,指向“void”的指针或其他任何东西的解释。我需要根据C语言标准得出一个答案。

可以将一个变量转换为void类型并将其传递给函数,然后在函数中将其转换回原始数据类型,例如void* thread_func((void)args),然后在thread_func函数中使用(struct args)args - Auxilus
1
@Auxilus 我怀疑这个。你会因为将void传递给不兼容类型的struct args参数而出现错误。 - explogx
2
不是同一个情况。我谈论的是void类型,它是一种无法完成的不完整类型,这与完整类型“指向void的指针”不同。 - explogx
1
可能是void在C中是否为一种数据类型?的重复。 - Btc Sources
3
void 是实际的类型;然而,函数参数列表中的 (void) 表示该函数不带参数,而不是它带有一个 void 类型的参数。(不能使用 void 类型的参数)。我的翻译保留了原文意思并尽可能通俗易懂,没有添加解释或其他内容。 - M.M
显示剩余3条评论
5个回答

11

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) 是一个例外,因为它表示函数不带参数,而不表示它带有 void
foo((void) a) 中,强制转换并不会使值“不存在”。它将 a 转换为类型 void。其结果是一个类型为 void 的表达式。该表达式“存在”,但没有值,并且不能在表达式中使用,因此在 foo((void) a) 中使用它会导致错误消息。

11

在C语言中,void类型的引入意味着“不关心”而不是“null”或“nothing”,并且它被用于不同的范围。

void关键字可以引用void类型void引用void表达式void操作数void函数。它还明确定义了一个没有参数的函数。

让我们来看一些例子。


void类型

首先,void对象存在,并且具有一些特殊属性,如ISO / IEC 9899:2017,§6.2.5类型所述:

  1. void类型包含一组空值;它是一个不完整的对象类型,无法完成。

指针

void*更有用的void引用是对不完整类型的引用,但本身已经定义良好,因此是完全类型,具有大小,并且可以像任何其他标准变量一样使用,如ISO/IEC 9899:2017,§6.2.5类型所述:

  1. 指向void的指针应具有与指向字符类型的指针相同的表示和对齐要求。

    类似地,指向兼容类型的限定或非限定版本的指针应具有相同的表示和对齐要求。

    所有指向结构体类型的指针应具有彼此相同的表示和对齐要求。

    所有指向联合类型的指针应具有彼此相同的表示和对齐要求。

    指向其他类型的指针不需要具有相同的表示或对齐要求。


强制转换为void

它可以用作转换以使表达式无效,但允许完成任何这种表达式的副作用。该概念在标准中解释,位于ISO/IEC 9899:2017,§6.3 Conversions,§6.3.2.2 void

  1. (不存在的)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;
}

上面的代码片段展示了抑制编译器警告的标准做法。将参数b强制转换为void类型作为一个有效的表达式,不会生成代码,并将b标记为已使用,避免编译器产生警告信息。

void函数

标准规范中的§6.3.2.2 void段落还涵盖了一些有关void函数的解释。这些函数不返回任何在表达式中可用的值,但是调用函数时仍会实现副作用。


void指针属性

正如之前所说,指向void的指针更加实用,因为它们允许以通用的方式处理对象引用,这是由其在ISO/IEC 9899:2017, §6.3.2.3 Pointers中解释的特性所决定的:

  1. 指向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;
}

在这个片段中,您可以返回一个通用指针,该指针可能取决于输入参数par的值。

void作为函数参数

在所谓的ANSI标准之后引入了在函数定义中使用void参数,以有效地区分具有可变参数和没有参数的函数。

来自标准ISO/IEC 9899:2017,6.7.6.3函数声明符(包括原型)

  1. 类型为void的未命名参数作为列表中唯一的项的特殊情况指定函数没有参数。

实际编译器仍然支持使用空括号进行函数声明,以实现向后兼容性,但这是一个过时的功能,将在未来版本的标准中被删除。请参阅未来方向- §6.11.6函数声明符

  1. 使用带有空括号的函数声明符(不是原型格式的参数类型声明符)是一个过时的功能。

考虑以下示例:

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并不会将其设置为null。因此,您仍然在尝试将void对象作为参数传递给没有任何参数的函数。


副作用

根据要求,这是有关副作用概念的简短说明。

副作用是指执行语句所产生的对象和值的任何改变,这些改变不是直接期望的效果。

int a = 0;
(void)b = ++a;

在上面的代码片段中,void表达式失去了直接作用,赋值b,但是作为副作用增加了a的值。
唯一解释其含义的参考资料可以在5.1.2.3程序执行中找到:
访问易失对象、修改对象、修改文件或调用执行这些操作的函数都是副作用,即执行环境状态的更改。一般情况下,表达式的评估包括值计算和副作用的启动。

谢谢您的回答。另外,当标准说“评估无效表达式以获取其副作用”时,这是什么意思? - explogx

6

根据C标准#6.2.5p19:

19 void类型由一组空值组成;它是一个不完整的对象类型,无法完成。

这表明void类型是存在的。

疑问1:

void foo(void); // This function takes no argument. Not the 'void' type.

正确。
来自C标准#6.7.6.3p10 [强调是本人加的]

10 如果参数列表中的唯一项是类型为void的未命名参数,则表示函数没有参数

这是一种特殊情况,他们不得不添加到语言语法中,因为void foo();已经有了不同的含义(void foo();并没有指定任何关于foo参数的信息)。如果不是因为void foo();的旧含义,void foo();将是声明没有参数的函数的语法。你不能从这个推广任何东西。这只是一个特例。

疑问2:

// 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标准#5.1.2.2.1p1 [我强调的是]

1 The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters:

    int main(void) { /* ... */ }
             ^^^^

疑问5:

extern void a; // Why is this authorised ???

这是被授权的,因为void是一个有效的类型,并且这只是一个声明。没有存储将分配给a

2
首先,声明一个void对象似乎很奇怪,这意味着你什么也没有声明。
void是一种无法完成的不完整对象类型。这主要定义了它在常规语境中的用途,即那些不为void提供特殊处理的语境。您的extern声明就是这样一个常规语境。在非定义声明中使用不完整的数据类型是可以的。
但是,您永远无法为该声明提供匹配的定义。
所以原型中的void表示无,而不是void类型。
正确。参数必须未命名。并且(void)组合给予特殊处理:它不是类型为void的一个参数,而是根本没有参数。
然而,我创建了一个指向void的指针,对其进行了解引用,并将“结果”分配给我的int变量。我得到了“不兼容类型”的错误。这意味着void类型在此处存在。
不是的。对void*指针应用一元*运算符是非法的。你的代码之所以无效,就是因为这个原因。你的编译器发出了一个误导性的诊断消息。从形式上讲,不需要让诊断消息恰当地描述问题的根源。编译器可以只说“嗨!”
void是一种类型。它是一种无法完成的不完整对象类型。

2
在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在你定义它之前是不存在的,因为它具有extern存储类,所以你需要在某个地方进行定义,当你要进行定义时,可以像这样:

a = 10;

编译器报错:

错误:‘a’的类型不完整

出自C标准6.2.5 类型

void类型包含一个空值集;它是一个不能被完全定义的不完全对象类型

6.3.2.2 void

一个void类型表达式(指具有void类型的表达式)的(不存在的)值不得以任何方式使用,也不得对此类表达式进行隐式或显式转换(除了转换为void)。如果将任何其他类型的表达式评估为void表达式,将丢弃其值或标识符。(void表达式用于其副作用)。

6.3.2.3 指针

指向void的指针可以转换为指向任何对象类型的指针。任何对象类型的指针都可以转换为指向void的指针,并再次转换回来;结果应与原始指针相等。

存储类说明符或类型限定符修改函数参数类型列表中的关键字void(6.7.6.3)。

尝试使用void表达式的值,或对除了void之外的void表达式应用隐式或显式转换(6.3.2.2)。


实际上,我阅读了标准,以便您可以取消引用空指针,但您不能使用该值... - Antti Haapala -- Слава Україні
@Prion 在 a = *p; 中唯一错误的是 *p,因为你没有对其进行正确类型转换,或者编译器不知道 p 的类型,因为它无法识别 void 类型。正确的版本是 a = *(int*)p;,因为 p 被强制转换为 int* 类型,编译器知道这里的 p 的类型,即使它只是临时转换。 - Achal
1
@AnttiHaapala是正确的,您可以直接取消引用指向void的指针,但是您不能使用其值,但是取消引用是被授权的。 - explogx
@AnttiHaapala 在这里,void foo(void); 我的理解是它只是让编译器识别“无”,而不是将void识别为类型。 - Achal
5
“void”不能被视为数据类型。但是,按照标准,它是一种类型:“C11 6.2.5 "Types" 19——“void”类型包含一组空值;它是一种不完整的对象类型,无法完成。” - HolyBlackCat
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接