为什么在char标准大小为1的情况下还要写`sizeof(char)`?

49
我在编写一些C代码时阅读了一些C代码,发现存在像这样的代码片段:

char *foo = (char *)malloc(sizeof(char) * someDynamicAmount);

我想问一下,分配char数组内存的更加C风格的方法是什么?是使用sizeof(char)并且预防未来标准的改变,还是直接使用数字而忽略它?

12个回答

66

更Cish的方式是

char* foo = malloc(someDynamicAmount * sizeof *foo);

引用变量而不是类型,这样就不需要类型。 并且不需要对malloc的结果进行强制类型转换(这很像C++)。


4
如果 foo 的类型被更改,这样做会更好,因为它仍然会分配正确的内存量。在 memcpy 中执行此操作尤其重要。 - progrmr
8
“which is C++ish” - 如果在 C++ 中出现使用 malloc 的情况,你需要将返回值进行强制类型转换。我猜这是一个误导性的 C 习惯用法,可能还存在于 C++ 之前。有趣的是,对于 C 代码,有一份 CERT 建议说你应该始终将 malloc 的返回值进行强制类型转换,而另一份建议则表示不应该这样做。 - Steve Jessop
16
我理解您的意思是,你可以选择重复类型(这可能会导致不同步并引入错误),或者重复变量名。除非您的变量影响到其他变量,否则更改变量名称且未更新 malloc 的参数将导致编译失败("foo 未定义"),因此这是更安全的一种重复方式。请注意,这里的“重复”指的是在代码中多次使用相同的类型或变量名。 - Steve Jessop
4
有意设计一个库,使其可以在C和C++中编译有时是正确的选择。认为用C++编译随机的C代码不需要修改就能运行是一种幻想。但这比让一个在32位平台上开发且没有太多关注可移植性的代码在64位平台上运行起来要容易些。 - AProgrammer
3
在C89之前,*alloc函数返回的是char *而不是void *,因此需要进行强制类型转换。这也是为什么这种做法很难被根除的一部分原因;老一辈的程序员可能出于习惯仍然这样做,当然任何看他们的代码或书籍的人都会效仿这种做法。我花了一段时间才意识到它已经不再必要,然后才停止这么做。 - John Bode
显示剩余9条评论

29

为了表明意图,我使用 sizeof(char)。如果有人决定将 foo 设置为 int,他知道他需要使用 sizeof(int) 才能使其继续正常工作。

或者省略它并使用数字

而且使用神奇数字不是很好的编程实践。


这不是魔法数字。我指的是具有某些整数值的变量。 - Eimantas
2
即使编译器设置为发出所有可能的警告,int *foo = malloc(sizeof(char) * some_quantity) 仍然可以编译通过而没有任何提示,从而悄悄地引入了一个错误。 - Blrfl
sizeof (*foo) 是正确的方式。 - Prof. Falken
1
@0x69:如果在系统中int不是四个字节或char不是八位时,这将会失败。标准允许两种情况,并且这两种情况都存在于现实世界中。如果您需要已知大小的整数,则<stdint.h>提供了处理此类情况的类型。 - Blrfl
@0x69: 如果有人期望在任何具备标准兼容编译器的系统上构建您的代码将会产生无错误的二进制文件,那么“可能”和“必须”一样严重。标准提供了保证正确行为的机制,而我看不到任何合法的理由去故意放弃它们。 - Blrfl
显示剩余2条评论

25

我认为最佳实践是编写sizeof(*foo)。这样,即使foo的类型发生更改,sizeof也不需要进行更正,你也不用担心。


11

比较:

float*baz = malloc(sizeof(float) * someDynamicAmount);
int  *bar = malloc(sizeof(int)   * someDynamicAmount);
char *foo = malloc(sizeof(char)  * someDynamicAmount);

比较:

float*baz = malloc(sizeof(float) * someDynamicAmount);
int  *bar = malloc(sizeof(int)   * someDynamicAmount);
char *foo = malloc(someDynamicAmount);

我喜欢第一个版本。你更喜欢第二个吗?


5
在某些情况下,如果代码片段是列表的一部分,这些列表之间关系密切,已经通过水平对齐来表达 -- 当你加入一个long long时,与其显示只添加了一行,你更希望看到增加了三行变化 -- 那么,写sizeof(char)是可以接受的。;-p - Steve Jessop
5
@Steve:关键不在于列表本身,而是不要因为你恰好保证了字符的大小就例外地处理一般模式。 - Armen Tsirunyan
2
...嗯,那就是我会为大小编写一些东西。如果由于某种原因不适合按AProgrammer的方式编写,则应为sizeof(char)。在实践中,对于读取缓冲区,我会编写char *foo = malloc(BUF_SIZE);,在那个几乎通用的代码中,我会编写char *foo = malloc(sizeof(*foo)*ARRAY_SIZE)。我不会为了将前者视为与后者相同的“一般模式”的一部分而向我的代码添加我认为是噪音的内容。 - Steve Jessop
5
程序员如果精通某种语言,他们就能训练出寻找该语言潜在问题的眼力。由于几乎所有的malloc都需要sizeof(),因此当你在代码中浏览时看到没有包含sizeof()的malloc函数,就要停下来查看其类型,但最后发现只是一个字符类型,这会浪费你不必要的时间并打断你目前的思考。这些停顿可能会累积并破坏你的工作流程。保持一致性是有优点的。 - Lie Ryan
1
@John Bode:如果是这样的话,如果你已经训练自己的感官去假设一个缺失的sizeof意味着你正在分配一个char缓冲区,那么你就会错过那些你真正想要sizeof的场合。(PS:你的方式不是正确的方式,C语言并不是为了从左到右阅读的人而设计的,C语言是由那些螺旋阅读的人设计的,正如变量类型声明语法所示:P) - Lie Ryan
显示剩余5条评论

7

您说得没错,从标准上来说,乘法是无关紧要的。但是有人养成了一种习惯,使得代码保持一致性。如果您始终使用sizeof(),无论类型如何,就不会忘记。

char *foo = (char *)malloc(sizeof(char) * someDynamicAmount);
int  *bar = (int  *)malloc(sizeof(int)  * someDynamicAmount);

7
常见的习语是:
T *p = malloc(N * sizeof *p);

或者

T *p;
...
p = malloc(N * sizeof *p);

这样,您就不必担心类型问题。

3
引用自C99标准,第6.5.3.4节《sizeof运算符》:
当应用于类型为char、unsigned char或signed char(或其限定版本)的操作数时,结果为1。当应用于具有数组类型的操作数时,结果是数组中的总字节数。当应用于具有结构或联合类型的操作数时,结果是这种对象中的总字节数,包括内部和尾随填充。
也就是说,按照标准,sizeof char == 1,而不是按照实现。因此,在为字符调用malloc()和类似情况下省略sizeof(char)是绝对正确的。在我看来,未来的C标准很可能不会允许实现特定大小的char,因为太多的代码已经依赖于它为1,并且向后兼容性在C中非常重要。
因此,这个问题只涉及风格而非正确性,我支持AProgrammer's answer的观点。

1

sizeof(char) 并不是为了使您的代码在标准发生可能更改时“未来可靠”。这通常表明您完全误解了 sizeof 的含义以及 C 语言中内存对象的整个基本模型,即所谓的“类型表示”。

sizeof 运算符之所以存在或有意义,唯一的原因是因为 C 指定对象在内存中具有“表示”,其最小单位为 unsigned char。如果没有这个规定,存储将更加抽象,就没有使用 sizeof 和相关指针算术的必要了。

sizeof 定义为以 char 为“单位”,即 sizeof(T)==N 表示类型 T 占用 Nchar。鉴于此,sizeof(char) 是完全无意义的;它试图测量一个 char 占用多少个 char


1
sizeof是以字节为单位定义的,而不是字符,字符对于sizeof来说是特殊情况,但这与说sizeof是以字符为单位有很大区别。 - jmoreno
1
实际上,他是正确的,sizeof()返回的是char的数量,而不是字节数。根据定义,sizeof(char)1 - Simon Richter
1
@R: C99 6.5.3.4 表示 sizeof 运算符返回其操作数的大小(以字节为单位),并且 当应用于类型为 char、unsigned char 或 signed char(或其限定版本)的操作数时,结果为 1。我在这里看到它被定义为字节。 - Thomas Eding
6
“这绝非完全的误解……”你认为不需要写明原因(因为它总是1),并不意味着其他人也一无所知。与你的不同意见并不构成“完全的误解”。 - Ed Staub
1
我同意语言有点过于强烈了。我已经将其改为“这通常是完全误解的一个迹象...” - R.. GitHub STOP HELPING ICE
显示剩余3条评论

0

这完全是编码风格的问题。有几种不同的风格存在。

实际上回答这个问题,人们会写

malloc(n * sizeof(char))

为了保持使用malloc的所有代码一致。下次他们可能需要int,然后他们可以以完全相同的方式编写代码。
malloc(n * sizeof(int))

因此,这样做的原因是为了保持编码风格的一致性。即使sizeof(char)确实保证始终为1,因此是多余的。这是编写自我记录代码的一种方式。


然而,在C语言中使用malloc最常见的方式可能是

type* t = malloc(n * sizeof(*t));

或完全等价:

type* t = malloc(n * sizeof *t);

由于sizeof运算符不会产生副作用,所以即使变量t尚未初始化,此代码也是安全的。


第三种可能的风格是非常严谨正确的,它将使用 数组指针,因为我们分配的实际上是数组。
type (*t)[n] = malloc( sizeof(type[n]) );

这可能是最正确的方法,就类型正确性而言。分配了数组的大小,然后我们用数组指针指向已分配的数组。

然而,这种风格的问题在于数组指针会增加语法上的额外复杂性:您必须将此数组解引用为(*t)[i],而不是t[i]。这使得代码更难以阅读。(并且作为附注,如果n不是整数常量表达式,则该代码无法在旧的、过时的C编译器上编译。)


-3

编码风格也可以写成:

char *foo = (char *)malloc(1 * someDynamicAmount);

但这样做是为了根据所需的基本数据类型数量增加动态分配内存。如果您想增加100个字符,它将增加100个字符。如果可能没有需要,但如果我们写101或102,那么这样做会浪费内存。根据基本数据类型进行操作不会浪费任何内存空间。


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