C 中的 void 类型

The void type in C

C中的void类型从各种不同的情况来看似乎很奇怪。有时它的行为就像一个普通的对象类型,例如 intchar,有时它只是没有任何意义(它应该如此)。

看看我的代码片段。首先,您可以 声明 一个 void 对象似乎很奇怪,这意味着您什么都不声明。

然后我创建了一个 int 变量并将其结果转换为 void,丢弃它:

If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (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 变量。我收到“类型不兼容”错误。 这意味着 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 标准的答案。

C中,void不能被认为是数据类型,它是用作关键字的关键字占位符代替数据类型以表明实际上没有数据。因此这个

void a;

无效。

在这里

void foo(void); 

void 关键字用于通知编译器 foo 不会接受任何输入参数,也没有 return 类型。

在下面的例子中

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(*(int*)p); 如果 foo() 声明是 void foo(int);.

注意这个

 (void)a;

不做任何事情所以你的编译器可能不会给出任何警告但是当你喜欢时

int b = (void)a;

编译器不允许,因为 void 不被视为数据类型。

最后这个

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

这只是一个声明而不是定义,a 在你定义它之前不存在,因为 aextern 存储 class,你需要在某处定义以及何时定义

a = 10;

编译器抛出错误

error: ‘a’ has an incomplete type

来自 C 标准 6.2.5 类型

The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.

6.3.2.2 void

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 指针

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.

A storage-class specifier or type qualifier modifies the keyword void as a function parameter type list (6.7.6.3).

An attempt is made to use the value of a void expression, or an implicit or explicit conversion (except to void) is applied to a void expression (6.3.2.2).

在C语言中引入了void类型,其含义比'null'或'nothing'更多,用于不同的范围。

void 关键字可以引用 void typereference to voidvoid expressionvoid operandvoid function。它还明确定义了一个没有参数的函数。

让我们来看看其中的一些。


void类型

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

  1. The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.

指针

比较有用的reference to void,或者void *,是引用了一个不完整的类型,但是本身是定义好的,然后是一个完整的类型,有大小,可以使用与 ISO/IEC 9899:2017、§6.2.5 类型:

中所述的任何其他标准变量一样
  1. A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.

    Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements.

    All pointers to structure types shall have the same representation and alignment requirements as each other.

    All pointers to union types shall have the same representation and alignment requirements as each other.

    Pointers to other types need not have the same representation or alignment requirements.


投射到 void

它可以用作 cast 来使表达式无效,但允许完成此类表达式的任何 side effect。此概念在 ISO/IEC 9899:2017、§6.3 转换、§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.)

转换为 void 的一个实际示例是它用于防止函数定义中未使用参数的警告:

int fn(int a, int b)
{
    (void)b;    //This will flag the parameter b as used 

    ...    //Your code is here

    return 0;
}

上面的代码片段显示了用于消除编译器警告的标准做法。对参数 bvoid 的转换充当不生成代码的有效表达式,并将 b 标记为已使用以防止编译器抱怨。


void 函数

标准的段落 §6.3.2.2 void,还涵盖了一些关于 void 函数的解释,这些函数不 return 表达式中可用的任何值,但无论如何都会调用函数来实现副作用。


void 指针属性

正如我们之前所说,指向 void 的指针更有用,因为它们允许以通用方式处理对象引用,因为它们 属性 在 [=144 中进行了解释=] 9899:2017, §6.3.2.3 指针:

  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.

作为实际示例,想象一个函数 return 根据输入参数将指针指向不同的对象:

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;
}

在此代码段中,您可以 return 一个可以依赖于输入 par 值的通用指针。


void 作为函数参数

在函数定义中使用 void 参数是在所谓的 ANSI 标准之后引入的,以有效区分具有可变参数数量的函数与具有 无参数的函数.

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

  1. The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.

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

  1. The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.

考虑以下示例:

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;

在上面的代码片段中,void 表达式失去了直接作用,分配了 b,但作为 副作用 增加了 a 的值。

标准中唯一的参考,解释含义,可以在5.1.2.3程序执行中找到:

  1. Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.

    Evaluation of an expression in general includes both value computations and initiation of side effects.

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) 中使用它会导致错误消息。

来自 C 标准#6.2.5p19:

19 The void type comprises an empty set of values; it is an incomplete object type that cannot be completed.

这表明 void 类型存在。

疑问1:

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

正确。
来自 C 标准#6.7.6.3p10 [强调我的]:

10 The special case of an unnamed parameter of type void as the only item in the list specifies that the function has no parameters.

这是他们必须添加到语言语法中的特殊情况,因为 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 [强调我的]:

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.

First of all, it seems strange that you can declare a void object, meaning you just declare nothing.

void是一个不完整的对象类型,无法完成。这主要定义了它在 常规 上下文中的用途,即不为 ​​void 提供 特殊处理 的上下文。您的 extern 声明就是此类常规上下文之一。在非定义声明中使用不完整的数据类型是可以的。

但是,您永远无法为该声明提供匹配的定义。

So the void in a prototype means nothing, and not the type void.

正确。该参数必须未命名。 (void)组合被给予特殊处理:它不是void类型的一个参数,而是根本没有参数。

But then, I created a pointer to void, dereferenced it, and assigning the “result” to my int variable. I got the “incompatible type” error. That means the void type does exist here.

没有。将一元 * 运算符应用于 void * 指针是非法的。由于这个原因,您的代码已经无效。您的编译器发出了 误导性 诊断消息。形式上,不需要诊断消息来正确描述问题的根源。编译器可能只是说 "Hi!".

Is void an actual type, or a keyword to means nothing ?

是一种类型。它是一个不完整的对象类型,无法完成。