为什么size_t不是C语言的关键字?

27

sizeof是C语言的一个关键字,它返回一个名为size_t的类型的大小。然而,size_t并不是一个关键字,而是主要在stddef.h以及可能其他的C标准头文件中定义的。

考虑这样一种情况:您想创建一个不包括任何C标准头文件或库的C程序(比如创建操作系统内核)。在这种代码中,可以使用sizeof(它是C语言的一个关键字,因此是该“语言”的一部分),但是它所返回的类型(size_t)是不可用的!

这难道不意味着C标准规范存在某种问题吗?能否澄清一下这个问题?


很多头文件在操作系统内核中是完全安全和合理的,可以包含在内。你所担心的问题在于链接库,而不是包含头文件。 - RBerteig
3
RBerteig: 仅就论证而言,假设我想创建一个只使用语言本身而不使用头文件/库的 C 程序,并且我想使用 sizeof。 - Ashwin Nanjappa
你需要自己用汇编语言编写实现库。如果没有头文件和库(及其相关函数),你几乎什么也做不了。 - Chris Lutz
标准头文件是编程语言的一部分。其中许多定义了标准库的接口,但在内核或深度嵌入式系统中不使用这些接口有很好的理由。但其他头文件主要定义与实现相关的有用功能,例如stddef.h、limits.h等。 - RBerteig
请注意,即使您只进行算术运算,编译器也会生成对其运行时提供的函数的函数调用。这就是为什么几乎所有由GCC编译的C代码都依赖于libgcc的原因。 - rubenvb
显示剩余5条评论
10个回答

46

它并不会直接返回类型为 size_t 的值,因为 size_t 本身不是一个具体的类型,而是一个未指定的内置类型的 typedef 别名。typedef 标识符(例如 size_t)与其相应的基础类型完全等效(并在编译时转换为其基础类型)。如果在您的平台上将 size_t 定义为 unsigned int,则在系统上编译时,sizeof 返回一个 unsigned int。size_t 只是一种方便的方式来保持可移植性,如果您只是通过名称显式使用它,则只需要在 stddef.h 中包含它。


4
+1,正确的答案。任何返回typedef的内容都不能真正地说是返回那个typedef。它返回typedef所代表的类型。如果你想要可移植性,就包含stddef.h文件。如果你不需要,就查找在你的平台上size_t的定义,并直接使用该类型。 - Daniel Earwicker
Volte:我现在看到这张图片了。所以,编译器无法“看到”size_t,但是使用一个类型来计算sizeof,该类型与随编译器一起提供的头文件中typedef为size_t的内容完全相同。 - Ashwin Nanjappa
正如我在回答中指出的那样,编译器在自身编译时确实“看到了”size_t的typedef - 从某种意义上说,它“知道”size_t。 - anon
Neil Butterworth:在你的回复中,我无法理解它,但在阅读 Volte 的回复后理解了 :-) 现在稍微复杂一些,如果一个交叉编译器的本机平台和目标平台需要不同的 size_t 大小,会发生什么呢?;-) - Ashwin Nanjappa
编译器看到的size_t最多只是实现细节,因为符合C标准的编译器可以用任何语言编写。sizeof返回类型等同于size_t是一个标准问题,但实际底层类型留给编译器的实现。 - Simon Broadhead
@Neil 在这种情况下,你不仅是错误的,而且是彻底错误的。 - Frank Crook

14

sizeof 是一个关键字,尽管它的名称和用法,但它像 +=< 一样是一个 运算符,而不是像 printf()atoi()fgets() 一样是一个 函数。很多人忘记了(或者根本不知道)sizeof 实际上是一个运算符,并且始终在编译时解析,而不是在运行时解析。

C 语言并不需要 size_t 才能成为一个可用的、一致的语言。这只是标准库的一部分。C 语言需要所有的运算符。如果 C 使用关键字 plus 来加法操作,那么你就可以将其作为运算符。

此外,我经常将 size_t 隐式转换为 unsigned int(以及常规的 int,但 Kernighan 和 Ritchie 迟早会因此惩罚我)。如果您愿意,可以将 sizeof 的返回类型分配给 int,但在我的工作中,我通常直接将其传递给 malloc() 或其他类似函数。


Chris:感谢您那清晰明了的解释。但是,这是否意味着 size_t 与 int/char 一样紧密地内置于 C 中?那么为什么要在头文件中定义它呢? - Ashwin Nanjappa
8
对于C99风格的可变长度数组,虽然并不常用(甚至没有得到支持),但sizeof在运行时被计算。 - Michael Burr
@Michael - 真的吗?该死。然而,C99的支持真的很差,所以我会继续使用我的malloc()、realloc()和free()一段时间来完成这项工作。 - Chris Lutz
2
@ChrisLutz 自从什么时候开始C99支持不好了?这对我来说是新闻。仅仅因为MSVC未能实现一个十多年的标准,并不意味着该标准支持不好... - rubenvb
@ChrisLutz:C11 通过使 VLA 支持变为可选项来解决了该问题。在我看来,更好的方法应该是使用一对宏,类似于 alloca,但作为分配/释放对工作,并要求块内的分配必须与该块内的释放相匹配。无法支持 alloca 样式语义的实现可以将分配/释放函数定义为 malloc/free 的同义词,但那些能够支持基于堆栈的语义的实现则可以提高性能并减少碎片化。 - supercat
显示剩余2条评论

9

C标准中定义了一些适用于自由环境的头文件,例如在操作系统内核中使用。它们不定义任何函数,仅定义和typedef。

它们是float.h、iso646.h、limits.h、stdarg.h、stdbool.h、stddef.h和stdint.h。

在操作系统上工作时,从这些头文件开始并不是一个坏主意。有了它们,你的内核许多事情就会变得更容易。特别是stdint.h将变得非常方便(uint32_t等)。


DevSolar:操作系统内核只是一个例子,相反考虑一种情况,我想使用sizeof编写C程序,但不包括任何C头文件。感谢您提供有关自由环境标头的信息,我之前并不知道。 - Ashwin Nanjappa

8
这是否意味着C标准规范存在某种问题?请查阅托管实现的C和自由实现的C之间的区别。自由实现(C99)需要提供以下头文件:
  • <float.h>
  • <iso646.h>
  • <limits.h>
  • <stdarg.h>
  • <stdbool.h>
  • <stddef.h>
  • <stdint.h>
这些头文件根本不定义任何函数。它们定义了语言的某些部分,这些部分在某种程度上是编译器特定的(例如,在<stddef.h>中的offsetof宏以及<stdarg.h>中的可变参数列表宏和类型),但是它们可以处理而无需将其构建为完整关键字。
这意味着即使在您的假想内核中,您也应该期望C编译器提供这些头文件和任何底层支持函数 - 即使您提供其他所有内容。

4

我认为 size_t 不是关键字的主要原因有:

  • 没有强制性的理由让它成为关键字。 C 和 C++ 语言的设计者总是倾向于在可能和合理的情况下使用库来实现语言特性。
  • 向语言中添加关键字可能会对现有的遗留代码造成问题。这也是他们通常不愿意添加新关键字的另一个原因。

例如,在讨论 C++ 标准的下一个主要修订时,Stroustrup 说到

C++0x 的改进应该以使得结果语言更易学习和使用的方式完成。 委员会的经验法则之一是:

...

  • 优先使用标准库设施而不是语言扩展

...


不过,内置运算符 sizeof 返回一个库类型 size_t 的表达式,这感觉相当奇怪。 :) - musiphil
1
@musiphil:也许这样想会感觉不那么奇怪:库必须定义size_tsizeof运算符产生的类型。 - Michael Burr

3

即使您正在开发内核,也没有理由不包含stddef.h - 它为特定编译器定义了类型大小,任何代码都需要。

还要注意的是,几乎所有的C编译器都是自编译的。因此,sizeof运算符的实际编译器代码将使用size_t并引用与用户代码相同的stddef.h文件。


Neil:但是就为了争论一下,假设我想创建一个不使用任何C标准头文件或库的程序。只是一个纯C语言程序。 - Ashwin Nanjappa
1
头文件是 C 语言的一部分! - anon
Neil Butterworth:在你的回复中我无法理解它,但是当我读到Volte的回复时就明白了 :-) 现在让事情变得有点复杂,如果一个交叉编译器的本地平台和目标平台需要不同的size_t会发生什么呢?;-) - Ashwin Nanjappa

2

size_t实际上是一种类型,通常是无符号整数。Sizeof是一个运算符,用于获取类型的大小。sizeof返回的类型实际上是与实现相关的,而不是C标准。它只是一个整数。

编辑: 非常清楚的是,您不需要size_t类型才能使用sizeof。我认为您要找的答案是 - 是的,它不一致。然而,这并不重要。 您仍然可以正确地使用sizeof而不需要从头文件定义size_t。


Anthony:我知道这一点。但是,size_t是在C头文件中定义的,它不是语言的一部分。这就是断开连接的地方。 - Ashwin Nanjappa
在C++标准中(我没有C标准),sizeof被定义为返回size_t而不仅仅是整数类型,并且该条款明确引用了stddef.h。 - anon
Ash:以下代码是有效的:int main() { double a; unsigned int b = sizeof(a); }即使没有任何头文件,这仍然是正确的。size_t 的最低公共分母是它是一个无符号整数类型。size_t 基本上只是一个 typedef。 - Anthony
Anthony Kanago:这也可能意味着您正在使用的编译器知道 size_t。如果我们变得过于追求严谨,这可能不是一个有效的情况,因为标准并没有说编译器应该知道 size_t。 - Ashwin Nanjappa

2

来自MSDN:

当对char类型的对象应用sizeof运算符时,它将产生1

即使您没有可用/包含stddef.h并且不了解size_t,使用sizeof您也可以相对于char获取对象的大小。


但是,如果您想将该大小存储在某个地方,而不会冒溢的风险,您将需要使用 size_t 的 typedef。 - anon
schnaader: 上面的意思是size_t将会是某种整数类型,但具体是哪种类型则不确定。 - Ashwin Nanjappa
@Neil - 说实话,你有多经常发现一个类型,sizeof() 返回的大小甚至会溢出无符号 char?在我的系统上,即使是 FILE 结构体也只有88字节。 - Chris Lutz

1

size_t 不是必须的关键字。不同的架构通常对整数类型有不同的大小。例如,如果他们没有决定将 int 作为 64 位数据类型,那么一个 64 位机器很可能会将 unsigned long long 作为 size_t

如果您将 sizeof 设为编译器内置类型,则会削弱进行交叉编译的能力。

此外,sizeof 更像是一个魔法编译时宏(类似于 C++ 模板),这就解释了为什么它是关键字而不是定义类型。


这个论点是无意义的,因为即使整数类型(例如)的大小也取决于平台。一个int可能是4字节或8字节等等,但int仍然是一个关键字。 - Ashwin Nanjappa

1

简单的原因是因为它不是基本类型。如果您查阅C标准,您会发现基本类型包括intchar等,但不包括size_t。为什么呢?正如其他人已经指出的那样,size_t是一种实现特定的类型(即一种能够容纳任何对象的“C字节”大小的类型)。

另一方面,sizeof是一个(一元)运算符。所有运算符都是关键字。


哪种基本类型不是特定于实现的? size_t 是一种特定于实现的类型,但这并不是它成为库类型的原因。 - musiphil

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