C中的void类型

gat*_*sec 7 c standards void language-lawyer semantics

void在各种不同的情况下,C中的类型似乎很奇怪.有时它的行为类似于普通的对象类型,例如intor char,有时它只是意味着什么(应该如此).

看看我的片段.首先,你可以声明一个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在原型中没有任何意义,而不是类型void.

但是,然后,我创建了一个指针void,取消引用它,并将" 结果 " 分配给我的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;
}
Run Code Online (Sandbox Code Playgroud)

void实际类型,还是关键字意味着什么?因为有时它的行为类似于" 这里不允许任何东西 "的指令,有时候就像一个实际的类型.

编辑:这个问题重复.它纯粹是关于void类型的语义.我不想要任何关于如何使用void,指针void或任何其他事情的解释.我希望按照C标准给出答案.

Eri*_*hil 10

void是一种类型.根据C 2018 6.2.5 19,类型没有值(它可以表示的值集是空的),它是不完整的(它的大小是未知的),并且它不能完成(它的大小不可知).

关于extern void a;,这没有定义对象.它声明了一个标识符.如果a在表达式中使用(除了作为sizeof_Alignof运算符的一部分),则必须在程序中的某处定义它.由于void在严格符合C的情况下不能定义对象,a因此不能在表达式中使用.所以我认为这个声明在严格符合C的情况下是允许的,但没有用.它可能在C实现中用作扩展,允许获取其类型未知的对象的地址.(例如,a在一个模块中定义一个实际对象,然后将其声明为extern void a;另一个模块,并&a在那里使用它来获取其地址.)

(void)作为参数列表的函数声明是一个kludge.理想情况下,()可能用于表示函数不带参数,如C++中的情况.然而,由于C的历史,()用于表示未指定的参数列表,因此必须发明其他东西以表示没有参数.因此(void)被采纳了.因此,(void)是一个例外,那会说,规则(int)是拍摄的功能int,(double)是拍摄的双重功能,等等- (void)是一个特殊的情况下这意味着一个函数没有参数,不,它需要void.

在中foo((void) a),强制转换不会使值"不存在".它会转换a为类型void.结果是类型的表达式void.该表达式"存在",但它没有值,不能在表达式中使用,因此使用它会foo((void) a)导致错误消息.


Fra*_*e_C 8

在C语言中,void类型的引入带有"不关心"的含义而不是"空"或"无",它用于不同的范围.

void关键字可以引用void type,一reference to void,一void expression,一个void operand或一个void function.它还明确定义了一个没有参数的函数.

我们来看看其中的一些.


void类型

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

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

指针

更有用reference to void,或者void *是对不完整类型的引用,但本身定义良好,然后是完整类型,具有大小,并且可以用作ISO/IEC 9899:2017中所述的任何其他标准变量, §6.2.5类型:

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

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

    所有指向结构类型的指针都应具有相同的表示和对齐要求.

    所有指向union类型的指针都应具有相同的表示和对齐要求.

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


投射到 void

它可以用作强制转换来使表达式无效,但允许完成此类表达式的任何副作用.这一概念在ISO/IEC 9899:2017标准中解释,§6.3转换,§6.3.2.2无效:

  1. 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;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码段显示了用于静音编译器警告的标准做法.铸到void参数的b作为不产生代码和标记的有效表达b所用的防止编译器抱怨.


void 功能

段落§6.3.2.2无效标准,也包括对void函数的一些解释,这些函数不返回表达式中可用的任何值,但是函数被调用以实现副作用.


void 指针属性

正如我们之前所说的,指针void更有用,因为它们允许以通用方式处理对象引用,因为它们的属性在ISO/IEC 9899:2017,§6.3.2.3中有说明:

  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;
}
Run Code Online (Sandbox Code Playgroud)

在此片段中,您可以返回可依赖于输入par值的通用指针.


void 作为函数参数

使用的void函数定义参数中后引入的,即所谓的,ANSI标准,以有效地消除歧义从具有功能可变数量的参数的函数没有参数.

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

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

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

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

请考虑以下示例:

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!
Run Code Online (Sandbox Code Playgroud)

现在类似于你的测试,如果我们调用函数bar如下:

int a = 1;
bar((void)a);
Run Code Online (Sandbox Code Playgroud)

触发错误,因为转换为void对象不会使其为空.所以你仍然试图将一个void对象作为参数传递给一个没有任何函数的函数.


副作用

根据要求,这是副作用概念的简短解释.

副作用是从执行声明中获得的对象和值的任何改变,而不是直接预期的效果.

int a = 0;
(void)b = ++a;
Run Code Online (Sandbox Code Playgroud)

在上面的片段中,void表达式失去直接效果,分配b,但由于副作用增加了值a.

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

  1. 访问易失性对象,修改对象,修改文件或调用执行任何这些操作的函数都是副作用,这些都是执行环境状态的变化.

    表达式的评估通常包括值计算和副作用的开始.


H.S*_*.S. 6

从C标准#6.2.5p19:

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

这表明该void类型存在.

疑惑1:

void foo(void); // This function takes no argument. Not the 'void' type.
Run Code Online (Sandbox Code Playgroud)

正确.
来自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);
Run Code Online (Sandbox Code Playgroud)

不,它不会因为void它也是一个对象类型,尽管它不完整.

疑问3:

// Assigning to 'int' from incompatible type 'void': so the 'void' type does exists...
a = *p;
Run Code Online (Sandbox Code Playgroud)

是的,它确实存在,因此编译器在此语句上报告错误.

疑惑4:

// Am I not passing the 'void' type ?
foo(*p);
Run Code Online (Sandbox Code Playgroud)

foo()功能声明:

void foo(void);
         ^^^^
Run Code Online (Sandbox Code Playgroud)

void参数列表中显示,因为它已被宣布不带参数的功能将不承担任何说法.
仅供参考,请从C标准#5.1.2.2.1p1 [emphasis mine]中查看:

1程序启动时调用的函数名为main.该实现声明此函数没有原型.它应该使用int的返回类型定义,并且没有参数:

    int main(void) { /* ... */ }
             ^^^^
Run Code Online (Sandbox Code Playgroud)

疑惑5:

extern void a; // Why is this authorised ???
Run Code Online (Sandbox Code Playgroud)

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