【问题标题】:Why can't we declare a variable of type void?为什么我们不能声明一个 void 类型的变量?
【发布时间】:2014-11-09 06:53:44
【问题描述】:

我正在标准中寻找对这一事实的正式解释。 我找到了 3.9.1/9 所说的内容,并尝试使用该部分进行解释。

第 3.9.1/9 节,N3797

void 类型有一组空值。 void 类型是一个 无法完成的不完整类型。它被用作回报 不返回值的函数的类型。任何表达式都可以 显式转换为 cv void (5.4) 类型。 void 类型的表达式 应仅用作表达式语句(6.2),作为操作数 逗号表达式 (5.18),作为 ? 的第二个或第三个操作数:(5.16), 作为 typeid、noexcept 或 decltype 的操作数,作为 返回类型为 void 的函数的返回语句 (6.6.3), 或作为显式转换为 cv void 类型的操作数。

我不明白 void 类型具有一组空值这一事实意味着什么?

假设类型 T 有一组空值。为什么编译器遇到以下行时会抛出错误:

extern T v; 

我们可以通过以下方式对不完整类型的变量进行 decal:

#include <iostream>
#include <cstring>

using namespace std;

struct Foo;

extern Foo f; //OK!

int main()
{
}

效果很好

DEMO

它不能在 void 类型上完成

#include <iostream>
#include <cstring>

using namespace std;

extern void f; //compile-time error

int main()
{
}

DEMO

【问题讨论】:

  • 如果类型有一组空值,你可以用这个变量做什么?
  • 它们的关键是“void 类型是一个不完整的类型无法完成”,这就是它与任何其他不完整类型的不同之处。
  • @K-ballo: struct Foo 是一个不完整的对象类型void 不是对象类型。这使得它在根本上有所不同。
  • @K-ballo:因为definition of object type 说不是。
  • @BenVoigt:隐式转换为uintptr_t 会启用一些奇怪的表达式,例如ptr + 1.0。对我来说,有一个“未指定地址”的类型是有意义的,尤其是在 C 中。但如果你要让它成为一个整数类型,我认为你应该需要在两个方向上进行强制转换。未拼写为 void* 的指针类型可能会满足所有小问题。

标签: c++ language-lawyer void


【解决方案1】:

您不能声明void 类型的变量,因为变量必须具有对象类型或者是引用,extern void f; 不声明引用,并且void 不是对象类型

第 3 节 [basic] 这么说

变量由非静态数据成员或对象以外的引用的声明引入。

第 3.9 节 [basic.types] 这么说

对象类型是(可能是cv-qualified)类型,它不是函数类型、引用类型,也不是void 类型。

【讨论】:

  • 所以? 7.1 在哪里声明对象类型是必需的?
  • @MSalters:7.1 涵盖了许多不同的声明。对象类型仅对对象的声明是必需的。但我不认为 Dmitry 声称 extern void f; 应该声明一个函数或一个引用。如果他确实这么说,那么很容易证明void 也不是函数类型,也不是引用类型。
  • @MSalters:此外,我认为控制声明的是第 8 节。第一句“声明器在声明中声明单个变量、函数或类型。”并且从第 3 节开始:“variable 由非静态数据成员或对象以外的引用的声明引入”
  • 后半部分最简单。声明引入变量这一事实并不意味着声明的所有内容都是变量。第 8 节涵盖了声明符,它们是声明的“非类型部分”。见 8/1。但是这里的void 是一个type-specifier,特别是一个simple-type-specifier (7.1.6.2)。
  • @MSalters:是的,它可以出现在 (1) 一个 typedef,(2) 一个函数声明中,它将作为返回类型,声明符必须包含 (),(3 ) 引用声明,其中声明符必须包含&amp;, (4) 指针类型的变量声明,其中声明符包含*。语法绝对允许void 成为类型说明符。但是规则不允许声明类型为void(单独)的变量。
【解决方案2】:

"void 类型是不完全类型"

您不能创建任何不完整类型的变量

“……无法完成”

虽然您的 extern 不完整结构示例可以在稍后完成,但编译器知道任何 void 类型的声明都无法完成。

【讨论】:

  • 我们不能定义不完整类型的对象,但可以声明这种类型的变量。 coliru.stacked-crooked.com/a/55b2c751ba86478f
  • 请注意,该帖子不是我正在寻找的答案。因为我问的是声明,而不是定义。 3.9/5 拒绝不完整类型的对象定义。
【解决方案3】:

[edit] 下面的答案给出了有效的观察结果,但它们相互矛盾。由于这些可能很有价值,我不会删除它们,但请参阅 Ben Voight 的回答和那里的 cmets 以获得更直接的方法。

7.1.1/8 明确允许您对 extern 声明的观察:

已声明但未定义的类的名称可以在外部声明中使用。这样的声明只能以不需要完整类类型的方式使用。

void 不是“已声明但未定义的类”,7.1.1 中没有其他适用的例外。

此外,3.9/5 相当明确地表明它实际上是允许的:

声明但未定义的类、某些上下文 (7.2) 中的枚举类型、大小未知或元素类型不完整的数组,是不完整定义的对象类型。 [45] 不完整定义的对象类型和 void 类型是不完整的类型 (3.9.1)。对象不应被定义为具有不完整的类型。

强调我的。这部分标准对定义和声明之间的差异非常具体,因此省略它指定声明允许的。

【讨论】:

  • 我认为你也可以声明外部变量,例如具有未知边界的数组类型?
  • 有趣。 @dyp 不是 int x[] 只是 int* 吗?
  • @Yakk 不,看看 [basic.types]/6。这样的不完全类型可以补全:extern int arr[]; /* ... */ int arr[10];(当然是函数参数之外)
【解决方案4】:

如果变量有一组空值,它就不能用于任何事情。

你不能给它赋值,因为没有可能的值可以赋值。

你不能访问它,因为你从来没有分配给它,所以它有一个不确定的值。

由于没有可能的值,因此没有变量的大小。

void 只是用作可变位置的占位符。它用作返回类型以指示函数不返回值。它在参数列表中的C 中使用,以指示该函数不接受任何参数(以解决该语言的预原型版本的歧义)。它与指针声明一起使用来创建可以转换为任何其他指针类型的通用指针。在变量声明中没有类似的用途。

【讨论】:

  • 你能看看我更新的答案吗?我澄清了我想了解的确切内容。
  • extern 声明要求被链接的其他模块之一具有该变量的定义。但是不能有一个定义void变量的模块,因为它会在里面放什么?
  • 所以我的标签struct my_tag {}; 同样没用。然而,我可以定义一个它的类型的对象,我可以分配给它,它有一个非零大小等等。
  • 你能给它分配什么?
  • 相同类型的新“值”/对象? my_tag x; x = my_tag{};
【解决方案5】:

因为 C 和 C++ 假设任何对象都可以通过比较它们的地址来比较其身份,所以它们必须确保所有对象都具有固定的非零大小。如果不是因为这个要求,实际上在很多情况下声明零大小的对象会有些用[例如在使用模板的代码中,这些模板包含有时有用有时不有用的字段,或者作为强制将结构填充到某种对齐方式的手段,要求它包含需要这种对齐方式的元素]。然而,事实上,零大小类型与规定每个对象具有唯一地址的规则不包括允许存在可以共享地址的零大小对象的例外情况不一致。

即使允许大小为零的对象,但是,“指向未知对象的指针”不应与“指向零大小对象的指针”相同。鉴于void* 类型用于前者,这意味着后者应该使用其他东西,这反过来意味着void 以外的东西应该是零大小的东西的类型对象点。

【讨论】:

  • 我认为这属于空基类优化的讨论;与void无关。
  • @BenVoigt:当有人问“为什么在 Y 语言中不允许使用 X”时,“直接”的回答几乎总是“因为 Y 的规范是这样说的”,但这通常不是很有信息量或有趣的。我认为在许多情况下,检查是否可以以任何有用的方式允许 X 而不引起问题会提供更多信息;在很多情况下,禁止某事,是为了至少避免一些因允许而导致的问题。
  • 但你所证明的只是(sizeof f) &gt;= 1。这可以通过制作1 == sizeof (void) 来轻松满足,就像已经为sizeof (empty_struct) 完成的一样。
  • @BenVoigt:如果关于对象唯一性的规则允许零大小的类型,并且如果指针指向任何东西的类型被称为 pointer 而不是 void*,那么为零-size void 可能具有与空结构不同的语义含义,即使后者的大小也为零。不过,对于占用空间的 void 类型,我看不到任何有用的语义含义。
  • 换句话说,如果你把void现在的所有行为,并为它创建新的关键字,你可以给void一个全新的含义,并改变语言来使它有用。但这如何帮助我们理解我们所拥有的语言中的void
【解决方案6】:

voidincomplete 类型 - 您可以声明指向它们的指针并在函数签名中使用它们。显然,extern Foo f; 允许的,因为 struct Foo 可以在另一个编译单元中定义(如果它是 not,则错误将被检测到链接器),但void 永远不能被“定义”(编译器当然知道这一点)所以void 在这种情况下非常特别。

【讨论】:

  • you can _only_ declare pointers and use them in function signatures. 严格来说并不正确。因为我引用了一个例子,它声明了一个不完整类型的变量
  • @DmitryFucintv 好的,但是它永远不能完全指定为struct Foo; 可以,所以这种方式很特别
  • @Yakk 谢谢,在答案中添加了一些(希望)地址
【解决方案7】:

嗯 - 我真的不明白这背后的理由。如果通过这种方式我们可以声明一个未知类型的变量,那就太好了。像'void *'和未知大小的数组。想象一下这样的代码:

#include <iostream>
#include <cstring>

using namespace std;

extern void f;

int main()
{
    cout << (int &)f << endl; //cout 'f' as it was integer
}

struct {
    int a;
    double b;
} f{};

您现在实际上可以对数组做类似的事情:

#include <iostream>
#include <cstring>

using namespace std;

struct Foo;

extern int arr[];

int main()
{
    cout << arr[2] << endl;
}

int arr[4]{};

生活example.

【讨论】:

    猜你喜欢
    • 2021-06-11
    • 2020-05-31
    • 1970-01-01
    • 2012-11-09
    • 1970-01-01
    • 2020-05-16
    • 2016-04-23
    • 1970-01-01
    相关资源
    最近更新 更多