代码之家  ›  专栏  ›  技术社区  ›  DaBler

在C语言中使用数组参数是否被视为不好的做法?

  •  1
  • DaBler  · 技术社区  · 5 年前

    当声明一个访问内存中几个连续值的函数时,我通常使用数组参数,比如

    f(int a[4]);
    

    就我的目的而言,它工作得很好。但是,我最近读到 the opinion of Linus Torvalds .

    所以我想知道数组参数是否被认为是过时的?尤其是,

    • 编译器是否可以利用此信息(数组大小)签出绑定访问,或者
    • 这种技术是否会带来一些优化机会?

    在任何情况下,指向数组的指针呢?

    void f(int (*a)[4]);
    

    请注意,此表单不容易出现“sizeof”错误。但是在这种情况下效率如何呢?我知道GCC生成相同的代码( link )总是这样吗?在这种情况下,进一步的优化机会是什么呢?

    2 回复  |  直到 5 年前
        1
  •  7
  •   zwol    5 年前

    如果你写

    void f(int a[4]);
    

    那有 确切地 对编译器的意义与

    void f(int *a);
    

    这就是为什么利纳斯有他这样的观点。这个 [4] 看起来像 它定义了数组的预期大小,但没有。当您试图维护一个大型且复杂的程序时,代码的外观和实际含义之间的不匹配非常严重。

    (一般来说,我建议人们 假设利纳斯的观点是正确的。在这种情况下,我同意他的意见,但我不会那么生气地说。)

    自C99以来,有一种变化 意思是它看起来是什么意思:

    void f(int a[static 4]);
    

    也就是说,所有来电者 f 必须提供指向至少四个数组的指针 int 如果他们不这样做,程序就没有定义的行为。这可以帮助优化器,至少在原则上(例如,它可能意味着循环 a[i] 里面 f 可以矢量化)。

    你的替代结构

    void f(int (*a)[4]);
    

    给出参数 a 另一种类型(“指向4 int数组的指针”而不是“指向int的指针”)。与此类型等效的数组表示法是

    void f(int a[][4]);
    

    这样写的话,应该立即清楚声明是适当的,当论点 f 是一个 二维的 数组的内部大小为4,但不是其他值。

    sizeof 问题是另一个棘手的问题;我的建议是避免需要使用 西泽 在函数参数上几乎不惜任何代价。做 扭曲要生成的函数的参数列表 西泽 在函数内部“右”出来;这使得 呼叫 正确的函数,调用函数的次数可能比实现函数的次数要多得多。

        2
  •  0
  •   John Bode    5 年前

    除非它是 sizeof 或一元 & 运算符,或是用于在声明中初始化字符数组的字符串文本, 表达 类型为“n元素数组 T “将转换为”指针“类型的表达式” T “,表达式的值将是数组中第一个元素的地址。

    将数组表达式作为参数传递给函数时:

    int arr[100];
    ...
    foo( arr );
    

    函数实际接收的是指向数组第一个元素的指针,而不是数组的副本。你的行为和你写的完全一样

    foo( &arr[0] );
    

    有一个规则是函数参数类型 T a[N] T a[] 调整到 T *a ,所以如果函数声明是

    void foo( int a[100] )
    

    它将被解释为你写的

    void foo( int *a )
    

    这有两个重大后果:

    • 数组是通过“引用”隐式地传递给函数的,因此对函数中数组内容的更改反映在调用方中(与字面上不同 其他类型 ;

    • 你不能用 西泽 确定传递的数组中有多少个元素,因为无法从指针获取这些信息。如果函数需要知道数组的物理大小才能正确使用它,则必须将该长度作为单独的参数传递 .

    在我自己的代码中,我不在函数参数列表中使用数组样式声明——函数接收的是指针,所以我使用指针样式声明。我可以看到使用数组样式声明的参数,主要是作为文档的一部分(这个函数期望有一个这样大的数组),但是我认为加强参数的指针性是很有价值的。

    注意,如果我调用

    foo( &arr );
    

    那么FOO的原型应该是

    void foo( int (*a)[100] );
    

    但这也是我称之为

    void bar[10][100];
    
    foo( bar );  
    

    就像你不知道参数 a 指向单个 int 或一系列中的第一个 int S,你不知道 bar 指向单个100元素数组,或指向100元素数组序列中的第一个。


    1. 这就是为什么 gets 函数在c99之后被弃用,并在c211中从标准库中删除了-没有办法告诉它目标缓冲区的大小,因此它会很高兴地将输入写入数组的末尾,并删除后面的内容。这就是为什么它是如此流行的恶意软件漏洞。