【问题标题】:sizeof() array defined with explicit lengths in function prototypes in C [duplicate]sizeof() 数组在 C 中的函数原型中以显式长度定义 [重复]
【发布时间】:2025-12-31 11:40:07
【问题描述】:

我今天偶然发现了一个奇怪的 C 语言,我无法理解其背后的原因。

如果我有这样的功能(假设是 32 位架构):

void printSize(char array[6]) {
    printf("%zd\n", sizeof array);
}

我会得到 4,这是指针的大小。我期望得到 6,这是函数原型中明确声明的大小。

我知道数组是通过引用传递的,并且底层类型是指针。我假设将数组的长度放入原型中将为编译器提供返回 6 所需的信息。

为什么 C 会这样做?另外,如果编译器甚至不能执行 sizeof() 并返回那个大小,那么在原型中放置一个大小有什么意义呢?

【问题讨论】:

  • “阵列衰减”或类似的搜索词可能是一个有用的搜索词。
  • 将大小放入参数中的唯一真正意义在于自我记录。数组参数衰减为指针,因此就编译器而言,大小无关紧要。
  • @PaulR 怎么样? void printSize(char (*array)[6]);void printSize(char (*array)[]);?
  • @PaulR 我知道这种衰减,但我假设将大小放在原型中会将长度信息恢复到编译器的函数范围。我的问题是为什么编译器不这样做。他们本可以让它以这种方式工作,但出于某种原因选择不这样做
  • @FredLarson 它没有,但请阅读整个内容......我理解为什么它们会衰减为指针。我不明白为什么设计选择忽略了声明/原型中提供的尺寸信息?他们是否认为这是不安全的,因为调用代码可以选择不实际遵守尺寸信息?

标签: c sizeof function-prototypes


【解决方案1】:

您观察到的行为是由C standard 强制执行的。第 6.7.6.3p7 节关于“函数声明符”状态:

将参数声明为“类型数组”应调整为 ''qualified pointer to type'',其中类型限定符(如果有)是 在数组类型派生的 [ 和 ] 中指定的那些。如果 关键字 static 也出现在数组类型的 [ 和 ] 中 推导,然后对于函数的每次调用, 相应的实际参数应提供对第一个的访问 数组的元素至少与指定的元素一样多 大小表达式。

所以编译器这样做的原因是标准规定他们必须这样做。此外,从实用的角度来看,这意味着您无法将实际指针传递给这样的函数。考虑是否将数组传递给参数类型为int * 的函数,然后传递给参数类型为int [5] 的函数,或者是动态分配的数组,即int *arr = malloc(5 * sizeof(int));

另外,请注意,这仅适用于多维数组的第一维。这意味着:

void foo(int arr[4][5])

相同
void foo(int (*arr)[5])

但不是:

void foo(int **arr)

【讨论】:

  • “所以编译器这样做的原因是因为标准规定他们必须这样做。”我的问题是为什么标准是这样写的?让编译器忽略提供的大小信息似乎是一个奇怪的设计选择
  • @StephenKlein:这种行为是在 C 语言出现原型之前几年就被强制要求的。换句话说,它又是“歇斯底里的葡萄干”——或者如果你愿意的话,它是“历史原因”。破坏现有代码是 C 标准委员会(正确地)努力避免的事情——如果不致力于将现有代码保留为工作代码,C90 标准就不会取得如此成功。
  • @JonathanLeffler 感谢您的回答!我打算重新问我的问题,因为我不是很满意......不破坏向后兼容性对我来说是有意义的,因为在这种情况下添加“un-decaying”会改变一些使用新编译器的旧程序的行为。我刚刚意识到的一个明显的后续问题非常重要:用旧标准编写的 C 代码是否总是向前兼容?或者另一种方式:任何 c90 代码都将使用具有相同逻辑程序的 c99 编译器进行编译?
  • 不——事情并不完全向前兼容,@StephenKlein。例如,C99 禁止隐式函数声明和“隐式int”。旧代码可能写了main(argc, argv) char **argv; { … }。 C99 禁止这样做。它需要(最好)int main(int argc, char **argv) { … } 或(绝对不是首选)int main(argc, argv) int argc; char **argv; { … }。两者都符合 C99,尽管非原型表示法是“过时”表示法。编译器通常比标准更宽松,并继续允许旧的符号,除非您要求它们符合。
  • @JonathanLeffler 有趣!再次感谢:-)
【解决方案2】:

数组不是“通过引用传递”的。 C 中的任何内容都不是“通过引用传递”的。一切都是按值传递的。

数组,当“传递”给函数作为参数时,衰减指向数组第一个元素的指针。

sizeof array 因此返回4(字节),因为这就是您的实现中指针的大小。


允许您声明数组参数大小的功能主要用于文档。如果您看到一个函数原型,例如void printSize(char array[6]);,您可以假设该函数将仅访问数组的第一个 6 元素,尽管该函数不必严格遵守这一点(如果没有,编译器也不会抱怨)。

【讨论】:

  • “数组不是“通过引用传递”。C 中没有什么是“通过引用传递”。一切都是通过值传递的。“我不喜欢你在这里试图讨价还价。将数组传递给 C 中的函数调用传递可以被认为是对数组的引用。 “允许您声明数组参数大小的功能主要用于文档。” 没有太多功能!您的回答没有解决问题的“原因”,这是重点
  • 最后一句话对我来说没有多大意义。
  • @machine_1 括号中的那个?我试图清除它。
  • @StephenKlein 想想这个例子:int x = 42; int *p = &x; someFunc(p); 在这里,您将 传递给函数。该值是对整数的引用,但它仍然是一个值(4 字节数字形式的地址)。
  • @bool3max 我理解你的例子。我的问题是:为什么编译器不能获取指向数组第一个元素的指针的值,以及函数定义中给出的显式大小,然后将其视为声明为该大小的数组函数内?对我来说似乎很奇怪。