"void的大小是多少?"

89

这个语句会产生什么结果?

void *p = malloc(sizeof(void));
编辑:问题的扩展。
如果在GCC编译器中sizeof(void)返回1,那么将分配1字节的内存并使指针p指向该字节。如果p开始时是0x2345,那么p++会递增到0x2346吗?我在谈论p而不是*p。

5
这是非标准的C语言。在许多编译器中,这会造成编译时错误。 - Johan Kotlinski
为什么你需要知道呢?理解这个问题可能会帮助我们回答它。 - Martin York
4
关于您所编辑的内容,是的,在GCC中(仅限于GCC),对void指针进行递增操作会使其值加1。如果您重视代码的可移植性,请不要滥用GCC赋予您的自由,这完全是非标准的。GCC也承认这一点,并使用“-std=c99 -pedantic”来提示用户。 - Jonathan Leffler
1
正则空类型提案:http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0146r1.html - alfC
sizeof(void)调用未定义行为。未定义行为是指在使用非可移植或错误的程序结构或错误的数据时,标准C没有强制限制的行为。它允许编译器做任何它选择的事情。可以将表达式评估为1(这是GCC的做法),抛出编译时错误,甚至让恶魔从你的鼻子里飞出来。 - undefined
10个回答

70

void类型没有大小;这样做会导致编译错误。出于同样的原因,您不能执行以下操作:

void n;

编辑: 令我惊讶的是,在GNU C中执行sizeof(void)实际上可以编译:

$ echo 'int main() { printf("%d", sizeof(void)); }' | gcc -xc -w - && ./a.out 
1

然而,在C++中它并不会这样:

$ echo 'int main() { printf("%d", sizeof(void)); }' | gcc -xc++ -w - && ./a.out 
<stdin>: In function 'int main()':
<stdin>:1: error: invalid application of 'sizeof' to a void type
<stdin>:1: error: 'printf' was not declared in this scope

35
GCC具有一个选项来处理“sizeof(void) == sizeof(char)”...请参见-Wpointer-arith。 - Jonathan Leffler
6
@Jon,这是给那些在80年代初刚开始学习编程的初学者准备的,标准并没有被广泛确定。请翻译此内容。 - unixman83
8
sizeof (void) 是违反规定的。符合标准的 C 编译器(默认情况下gcc不符合)至少要警告它,并且可以(我个人认为应该)拒绝它。参考文献:N1570 6.5.3.4p1 (void是不完整类型)。自从1989年标准引入了void,情况一直如此。 - Keith Thompson
4
在1989年的ANSI C标准中,void被引入,并且此标准还将sizeof(void)视为一种约束违规。因此,@unixman83所说的话是不合理的。请注意,void在此之前并不存在。 - Keith Thompson
1
Sizeof void是编译的,因为你需要它来处理void*指针。 - kelin
1
回复:“你不能像 void n; 这样做”:FYI:但是,你可以这样做 extern void n; - pmor

47
如果您使用GCC并且未使用删除编译器特定扩展的编译标志,则sizeof(void)为1。 GCC具有执行此操作的非标准扩展
通常,void是不完整的类型,并且您不能对不完整的类型使用sizeof。

2
选项“-Wpointer-arith”由“-pedantic”隐含。我总是使用“-pedantic”。 :) - Josh Lee
1
@jleedev确实,我也尽可能地使用-pedantic。不过,有些第三方库头文件会让gcc -pedantic非常难过:( - hrnt
1
+1 真的,为什么GCC有成千上万个无用的扩展? - user142019
@user142019:因为它们很有用?并且可以通过不同的编程语言和硬件架构获得一致的后端模型。 - kriss

19

虽然 void 可以代表一种类型,但它实际上不能保存任何值。因此,在内存中它没有大小可言。获取 void 的大小是没有定义的。

void 指针 仅仅是一种语言结构,意味着一个指向 未命名 内存的指针。


1
那又怎样?任何空结构体在内存中也不应该占用任何空间,这应该是一个问题。实际上我想知道,在C++中空结构体是否真的使用0字节(就像在C中一样),因为sizeof对它们返回1。 - kriss
1
@kriss 我不确定你在说什么,但正如你自己注意到的那样,空结构体出于对象标识的原因确实占用内存。 - Konrad Rudolph
1
确实,在C语言中并非如此,使用内存来存储空对象感觉很邪恶。 - kriss
"语言结构"是我心中关于void是否可以占用内存的问题的关键。这个问题的出现是基于对C++原则的void * p解释,导致得出结论:p指向void类型的内存位置,这是错误的! - Naghi

17

void没有大小。在C和C++中,表达式sizeof(void)都是无效的。

在C语言中,引用N1570 6.5.3.4第1段:

sizeof运算符不得应用于具有函数类型或不完整类型的表达式、该类型的带括号名称或指定位域成员的表达式。

(N1570是2011年ISO C标准的草案。)

void是不完整类型。这一段是一个约束条件,意味着任何符合C编译器必须诊断它的任何违规行为。(诊断信息可以是非致命警告。)

C++11标准的措辞非常相似。这两个版本是在提出这个问题之后发布的,但规则可以追溯到1989年的ANSI C标准和最早的C++标准。事实上,void是不完整的类型,不能应用sizeof的规则正好回溯到void被引入语言的时候。
gcc有一个扩展程序,将sizeof (void)视为1。默认情况下,gcc不是符合C编译器,因此在其默认模式下,它不会警告sizeof (void)。即使对于完全符合C编译器,也允许使用此类扩展程序,但仍然需要进行诊断。

我猜他们允许 sizeof(void) == 1 是为了与 void 指针上的指针算术保持一致,这也是 gcc 扩展。引用他们的文档:其结果是 sizeof 也可以用于 void 和函数类型,并返回 1。。但是,由于标准要求,C 应默认提供诊断消息。 - Grzegorz Szpetkowski
2
@GrzegorzSzpetkowski:(几年后回复。)是的,标准要求进行诊断,但gcc默认情况下不是符合C编译器。从逻辑上讲,C标准对不声称符合其标准的实现没有任何要求。通过正确的命令行选项,如“-std=c11 -pedantic”,可以使gcc成为(非常接近)符合标准的编译器。使用这些选项,它确实会发出所需的诊断。 - Keith Thompson

12

获取void类型的大小是GCC扩展


6

sizeof() 不能应用于不完整类型。而 void 是无法完成的不完整类型。


4

在C语言中,在GCC编译器中,sizeof(void) == 1,但这似乎取决于你的编译器。

在C++中,我得到以下结果:

在函数'int main()'中:
第二行:错误:void类型不能使用'sizeof'
由于-Wfatal-errors,编译终止。

3
只有在GCC中,sizeof(void) == 1; 在标准中,这是未定义的行为。 - Jonathan Leffler
更新以添加GCC特定说明。 - Greg Hewgill
2
@JonathanLeffler:这不仅是未定义的行为,它还违反了约束条件。请参阅N1570 6.5.3.4p1。 - Keith Thompson
不仅仅是GCC,还包括任何与之兼容的编译器(包括clang、icc、tcc等)。 - Keith Thompson

0
关于第二个问题:请注意,sizeof(void *)!= sizeof(void)。在32位架构上,sizeof(void *)为4字节,因此p++将相应地设置。指针增加的量取决于它所指向的数据。因此,它将增加1个字节。

1
所以p指向1个字节的数据,因为sizeof(void)返回1,所以p++意味着p会增加1而不是4? - Lakshmi
1
指针递增的数量取决于它所指向的数据。所以是的。 - user59634
p 是 void(空指针)类型,而不是 void。因此,它将按照 void 类型的大小进行递增(在 32 位系统中通常为 4)。 - Technowise
4
@Technowise:不是的。 任何类型为 T* 的指针在递增时都会加上 sizeof(T)(而不是 sizeof(T*),后者几乎总是相同的,除了 或许 函数指针)。当它被递增时,唯一的例外当然是 void(再次例外的情况是启用了GCC扩展功能)。 - Konrad Rudolph
1
@Amit:你是怎么得出 void* 指针所指向的数据大小为 1 字节的结论的?sizeof(void) 是一个约束违规。(GCC 扩展将其视为 1。) - Keith Thompson

-1

虽然 sizeof(void) 本身可能没有意义,但在进行任何指针运算时非常重要。

例如:

 void *p;
 while(...)
      p++;

如果sizeof(void)被认为是1,那么这将起作用。 如果sizeof(void)被认为是0,则会进入无限循环。

4
这个示例代码片段在 C 和 C++ 中是非法的。一些编译器仍然允许它,但不建议使用。 - James
1
不,这一点都不重要(尽管gcc的作者们似乎认为它很重要)。如果你想在字节指针上进行指针算术运算,请使用“unsigned char*”。 - Keith Thompson
@KeithThompson - 即使是 char 也不能保证是1个字节。我曾经在一个将其设置为32位的系统上工作过。 - ash
@ash:是的,char保证是1个字节。如果您曾在带有32位char的系统上工作,则该系统上的字节为32位(CHAR_BIT == 32)。这就是C标准定义“byte”的方式。C标准,6.5.3.4的第2和第4段:“**sizeof运算符返回其操作数的大小(以字节为单位)。[...]当sizeof应用于具有类型 char unsigned charsigned char**(或其有资格的版本)的操作数时,结果为1。” - Keith Thompson
@KeithThompson 讲得很有道理。了解正在使用的标准非常重要。 - ash
显示剩余2条评论

-1

大多数C++编译器选择在尝试获取sizeof(void)时引发编译错误。

在编译C时,gcc不符合标准,并选择将sizeof(void)定义为1。这看起来很奇怪,但有一个原理。当进行指针算术运算并添加或移除一个单位时,意味着添加或移除所指向对象的大小。因此,将sizeof(void)定义为1有助于将void*定义为指向字节(未类型化的内存地址)的指针。否则,使用指针算术运算时会出现令人惊讶的行为,例如p + 1 == p,其中p是void*。虽然在c++中不允许对void指针进行这样的指针算术运算,但在使用gcc编译C时可以正常工作。

标准推荐的方法是使用char*来实现这种目的(指向字节的指针)。

C和C++在使用sizeof时存在另一个类似的差异,当您定义一个空结构体时:

struct Empty {
} empty;

我使用gcc作为我的C编译器,sizeof(empty)返回0。但是使用g++相同的代码将返回1。

我不确定C和C++标准在这一点上的规定,但我认为定义一些空结构/对象的大小有助于引用管理,以避免两个引用到不同连续对象(第一个为空)获得相同的地址。如果引用使用隐藏指针实现,如常所做的那样,确保不同的地址将有助于比较它们。

但这只是通过引入另一个问题(即空对象,即使是POD也会占用至少1字节的内存)来避免令人惊讶的行为(角落情况下引用的比较)。


7
“C选择将sizeof(void)定义为1。”- 不,那是错误的。在C和C++中,sizeof(void)没有定义,GCC在这一点上并不符合规范,void指针的指针算术也没有被定义 - 'void* p; p++;' 是无效的。即使使用“-pedantic”标志进行编译,GCC也会给出警告。关于GCC对C ++的默认行为,您也是错误的:默认情况下*this会被视为一个错误。 - Konrad Rudolph
@Konrad:是的,我非常确定旧版本的gcc不是这样的,但是现在更新的版本确实如你所说。 - kriss
至少在C语言中,一个空的struct定义是语法错误;gcc的支持是一种扩展。它们可能在C++中是有效的,但我不确定。 - Keith Thompson

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