【问题标题】:Why is it possible to declare an array in C without defining its length?为什么可以在 C 中声明一个数组而不定义其长度?
【发布时间】:2019-03-11 14:53:38
【问题描述】:

在 C 中,您应该定义数组的长度。但是,下面这段代码是有效的:

int arr[] = {10, 20, 30, 40, 50};

为什么这是可接受的语法?

【问题讨论】:

  • 没有人说你应该这样做,但一件重要的事情是,如果你将它作为函数的参数传递并且你没有指定它的长度。
  • 简短回答:初始化器还定义了数组的长度。

标签: c arrays initialization


【解决方案1】:

这是一个方便的功能。数组的大小是从初始化器推导出来的,所以你不必拼写它:

int arr[] = {10, 20, 30, 40, 50};

等价于

int arr[5] = {10, 20, 30, 40, 50};

另一个例子(感谢 Eugene Sh.)是字符串初始化器:

char str[] = "asd";

等价于

char str[4] = "asd";

要知道的重要一点是,当用作函数参数的类型时,情况会有所不同。以下所有形式:

void foo(int v[])

void foo(int v[1])
void foo(int v[5])
void foo(int v[1000])

它们之间都是等价的,它们被转换成这样:

void foo(int* v)

始终使用后者 (void foo(int* v)),而不是其他的。因为第一种形式让它看起来像你有一个数组类型,但实际上你拥有的是一个指针。这是误导。

【讨论】:

  • @Meerkat 请再问一个问题。这个和原来的有很大的不同。
  • @Meerkat 见MCVEHow-to-Ask 指南中也提到了这一点
  • @bolov IMO void (int f[]) 可以,但 void (int f[5]) 可能会产生误导。但这是相当自以为是的。但无论如何你都有我的紫外线
  • @Bolov 但那是 c++ 而不是 c
  • @Meerkat 它做的不止这些。是的,它完全抛弃了数字,但是它将看起来像数组类型的东西转换为指针类型。
【解决方案2】:

为了补充现有答案,引用C11,第 §6.7.9 章,P22

如果一个未知大小的数组被初始化,它的大小由最大的索引决定 具有显式初始值设定项的元素。数组类型在其末尾完成 初始化列表。

因此,数组的大小将由“最大索引元素”决定,或者简单地说,就是初始化列表中存在的元素数量。

【讨论】:

  • "最大索引元素" 允许指定初始化器语法,如 int arr[] = {[5] = 1}; 来设置第 5 个元素,而无需为前面的元素拼出零。 godbolt.org/z/f8jrTTsT5
【解决方案3】:

这是可以接受的,因为整数的大小(以字节为单位)在编译期间是已知的,因此编译器知道整个列表需要多少空间。

但要理解这个答案,必须深入挖掘并询问为什么在编译期间知道确切的大小如此重要。一般而言:为您的程序定义virtual address space。 其中一部分是存储局部变量的堆栈,并且不能与堆内存(malloc 工作的地方)混淆。堆栈是一个后进先出列表,还包含所有函数调用及其参数。它用于函数的末尾跳回,您来自哪里,并为此存储了一个内存地址。当你在你的函数中时,你放在堆栈上的所有东西都必须被释放,以便获得正确的跳转地址并避免潜在的段错误。

幸运的是,C 会自动为我们进行这种类型的内存管理,并在我们认为“超出范围”时释放我们所有的 automatic variables。为此,我们需要我们压入堆栈的确切大小,这就是编译器需要知道该大小的原因。

要说明编译器如何翻译您的代码并对这些数字进行硬编码,请参见此处:

$ echo "int int_size = sizeof(int); int main(void) { int arr[] = {10, 20, 30, 40, 50}; }" |\ 
    gcc -c -xc -S -o- -masm=intel - 


    .file   ""
    .intel_syntax noprefix
    .text
    .globl  main
    .type   main, @function
# [...] removed int_size here to keep it shorter. its "4" ;)
main:
.LFB0:
    .cfi_startproc
    push    rbp               # < backup rbp / stack base pointer
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov rbp, rsp              # < rsp / stack shift pointer = top of the stack
    .cfi_def_cfa_register 6
    sub rsp, 32
    mov rax, QWORD PTR fs:40
    mov QWORD PTR -8[rbp], rax
    xor eax, eax
    mov DWORD PTR -32[rbp], 10  # < 10 is one element from the array
    mov DWORD PTR -28[rbp], 20  # < -28 means relative to the top of the stack
    mov DWORD PTR -24[rbp], 30 
    mov DWORD PTR -20[rbp], 40
    mov DWORD PTR -16[rbp], 50
    mov eax, 0
    mov rdx, QWORD PTR -8[rbp]
    xor rdx, QWORD PTR fs:40
    je  .L3
    call    __stack_chk_fail@PLT
.L3:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 8.2.1 20181127"
    .section    .note.GNU-stack,"",@progbits

【讨论】:

  • 非常感谢!很好的答案!
  • C 允许自动存储运行时可变数组大小。 (当然不是使用初始化器,但int arr[n] 是允许的)。只有在全局范围内(或函数中的static int arr[] = {1,2,3};),编译器才需要知道大小,无论是从初始化列表的长度还是从[] 中的常量大小。 TL:DR:我认为您通过提出堆栈分配来过度复杂化。这很好理解,但与包含[5] 时的工作方式没有什么不同。
猜你喜欢
  • 1970-01-01
  • 2013-08-02
  • 2019-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-24
  • 2012-05-28
  • 1970-01-01
相关资源
最近更新 更多