【问题标题】:C Array versus Named VariablesC 数组与命名变量
【发布时间】:2021-06-22 01:03:49
【问题描述】:

如果这是指向数组第一个元素的指针:

int foo[5];

那么给每个成员贴上标签不是更有效吗:

int foo0, foo1, foo2, foo3, foo4;

第一个看起来更整洁,但它使用间接,因为变量foo实际上是一个指针?

编辑:我仍在等待真正的答案。我正在尝试了解它是如何工作的。我不是在寻求有关最佳实践的建议。谢谢。

【问题讨论】:

  • 视情况而定。如果你想迭代元素怎么办?如果你有几十个项目怎么办?等等等等
  • 变量foo 是一个数组,而不是一个指针。这些数字是什么意思?它们是代表不同的事物还是只是同一种事物的实例?
  • 访问单个变量可能比访问数组成员的效率略高。但是对于现代编译器和现代硬件,差异很可能会被忽视。无论如何,如果您需要一个数组,请使用它。没有数组(或指针),很多事情都无法完成。
  • foo 是一个数组,而不是一个指针
  • 您会喜欢comp.lang.c faq 的第 6 部分...以及其他部分

标签: arrays c pointers indirection


【解决方案1】:

foo[3] 应该生成与foo3 完全相同的代码,因为无论哪种情况,编译器都知道编译时的地址。通常,表达式foo[i] 需要更长的时间,因为它必须运行代码来根据foo 的位置和i 的值计算地址。当您需要遍历值时使用数组,这就是它的用途。不使用时使用foo0foo1 等。

【讨论】:

    【解决方案2】:

    在此声明中

    int foo[5];
    

    您声明了数组类型int[5] 的对象。对象foo 不是指向数组第一个元素的指针。要查看差异,请考虑以下演示程序。

    #include <stdio.h>
    
    int main(void) 
    {
        int foo[5];
        int *foo_ptr = &foo[0];
        
        printf( "sizeof( foo ) = %zu\n", sizeof( foo ) );
        printf( "sizeof( foo_ptr ) = %zu\n", sizeof( foo_ptr ) );
    
        return 0;
    }
    

    程序输出是

    sizeof( foo ) = 20
    sizeof( foo_ptr ) = 8
    

    或者考虑其他方案

    #include <stdio.h>
    
    int main(void) 
    {
        int foo[5];
        int *foo_ptr = &foo[0];
        
        int ( *p1 )[5] = &foo;
        int **p2 = &foo_ptr;
        
        printf( "sizeof( *p1 ) = %zu\n", sizeof( *p1 ) );
        printf( "sizeof( *p2r ) = %zu\n", sizeof( *p2 ) );
    
        return 0;
    }
    

    程序输出又是

    sizeof( *p1 ) = 20
    sizeof( *p2r ) = 8
    

    指针类型p1p2 之间甚至存在差异。

    此外,指针可能是可分配的,而数组是不可修改的左值。

    那是你可能不会写例子

    int foo[5] = { 1, 2, 3, 4, 5 };
    int bar[5] = { 5, 4, 3, 2, 1 };
    
    foo = bar;
    

    但你可以写

    int foo[5] = { 1, 2, 3, 4, 5 };
    int bar[5] = { 5, 4, 3, 2, 1 };
    
    int *foo_ptr = foo;
    int *bar_ptr = bar;
    
    for_ptr = bar_ptr;
    

    但实际上,除了极少数例外,数组指示符会转换为指向其第一个元素的指针。

    例如在这个声明中

    int *foo_ptr = foo;
    

    用作初始化器的数组指示符被转换为指向其第一个元素的指针。

    来自 C 标准(6.3.2.1 左值、数组和函数指示符)

    3 除非它是 sizeof 运算符的操作数或一元 & 运算符,或者是用于初始化数组的字符串文字, 具有“类型数组”类型的表达式被转换为 类型为“pointer to type”的表达式,指向初始 数组对象的元素并且不是左值。如果数组对象 有注册存储类,行为未定义。

    按照您的建议标记单个元素

    int foo0, foo1, foo2, foo3, foo4;
    

    意义不大。

    例如,一个数组可以包含非常多的元素,例如一千个元素甚至更多。

    你会写这样的声明吗?

    int foo0, foo1, foo2, foo3, foo4, /*other variable declarations...*/, foo1000;
    

    另一个问题如何将所有变量传递给函数?

    您需要列出所有变量。此外,当您将数组传递给函数时,实际上数组的所有元素都是通过引用传递的。也就是说,该函数将通过指向其第一个元素的指针来处理数组的原始元素。将所有“标记元素”传递给函数意味着该函数将处理元素的副本。您需要向每个“标记元素”传递运算符地址。

    另一个问题是 C 标准没有指定在每个顺序中局部变量将被放置在内存中。例如,编译器可能会放置从 foo0 或 foo1000 开始的变量。

    还有一个问题是如何编写一个循环来处理变量?您可能不会使用指针来遍历所有变量,因为如果对象属于数组,您可能会使用指针来遍历对象。

    或者如何动态分配所有变量并作为数组访问?

    如果你会写例如

    int *p0 = malloc( sizeof( int ) );
    ...
    int p1000 = malloc( sizeof( int ) );
    

    那么没有任何保证分配的对象将存储在相邻的内存范围中。

    【讨论】:

      【解决方案3】:

      第一个看起来更简洁,但它使用间接,因为变量 foo 实际上是一个指针?

      是和不是。

      C 标准使用抽象计算机来描述 C。它描述p[2] 的行为,好像数组p 被转换为指向其第一个元素的指针,然后添加2 以生成指向第三个元素的指针(索引2) ,然后访问该元素。

      但是,编译器不需要盲目地重现该抽象机器的确切行为。允许优化它生成的代码。如果您启用任何级别的优化,好的编译器将会。如果你在函数中声明int foo[5]并在函数中使用foo[2],编译器会知道foo在哪里,它会知道foo[2]在哪里,它会以同样的方式访问foo[2]如果您声明 int foo0, foo1, foo2, foo3, foo4;,请访问 foo2。它很可能是堆栈指针或堆栈帧指针或类似基址寄存器的偏移量。当您在代码中使用foo[2] 时,它实际上不会进行指针计算。

      使用foo[2],编译器会在其内部数据库中查找foo,进行指针计算,然后生成直接访问foo[2]的代码。使用foo2,编译器将在其内部数据库中查找foo2,然后生成直接代码以访问foo2。只要用常量索引访问数组,最终的结果都是一样的。

      鉴于此,我们可以回答您的问题:

      那么给每个成员贴上标签不是更有效吗:…

      不,因为运行时代码是相同的。而且,在编译时,创建一个名为 foo 的数组并在内部数据库中对其进行跟踪对于编译器来说可能比创建五个变量并全部跟踪它们要少。

      【讨论】:

        【解决方案4】:

        如果这是指向数组第一个元素的指针:

        它不是,它一个数组。数组不是指针。指针不是数组。

        当在大多数表达式中使用或声明为函数参数时,数组“衰减”为指向数组第一项的指针。这意味着在许多情况下,数组名称通常可以像指针一样使用。这不会使数组成为指针。

        如果邮递员收到一封写有您住所地址的信,他们就知道该把信投递到哪个住所。这并不意味着信上的文字是房子,或者你的房子现在是写在信上的文字。

        那不是更有效率吗……

        不,因为数组保证分配在一个连续的内存区域。个别变量没有这样的保证。此外,像这样的例子的代码效率正是初学者甚至不应该费心思考的东西。手动代码优化是一个高级主题,需要大量经验和系统知识。

        处理一个可变大小数组的函数是否会比只使用 5 个相同类型参数的函数慢?好吧,也许,也许不是……这取决于 ABI 和调用约定、参数如何传递、可以使用多少 CPU 寄存器、数据缓存中会发生什么(如果可用)等等。

        但是采用可变大小数组的函数更强大、更易于维护、更易于阅读。因此,在大多数用例中,这是出于这些原因的正确解决方案。

        此外,像您的示例中的单个变量必然会产生大量代码重复,这更难维护,并且是潜在的错误来源。

        而不是:

        for(size_t i=0; i<n; i++)
          if(arr[i] == something)
            do_stuff(arr[i]);
        

        你最终会得到:

        if(foo0 == something)
          do_stuff(foo0)
        if(foo1 == something)
          do_stuff(foo1)
        ...
        

        这很快就会升级为大量代码重复,如果代码远比这些简单的示例复杂得多,那就更糟糕了。此外,这意味着您的可执行文件的大小会增加。

        【讨论】:

          【解决方案5】:

          一些想法:

          1. 不要试图在问题出现之前解决它,比如你的情况下的性能问题。

          2. 不要试图比编译器更聪明,那是浪费时间。

          3. 如果为了便于阅读,您需要将变量命名为foo0foo1foo2,而不是foobarbaz,最好将它们命名为foo[3]。否则,foobarbazfoo0foo1foo2 更具可读性。

          【讨论】:

            【解决方案6】:

            为什么数组更好

            1. 您可以迭代。
            2. 你可以索引
            3. 您可以将引用传递给其他函数(假设您有 10000 个命名变量并且需要将它们作为参数传递)

            【讨论】:

              【解决方案7】:

              这是一个通用的设计问题。

              简单的变量和数组有不同的用法。如果您打算迭代相同类型的一系列变量,这意味着这些变量应该是一个数组。

              这里不应该考虑性能,因为这类索引很常见并且处理得非常有效。

              【讨论】:

                猜你喜欢
                • 2011-10-10
                • 1970-01-01
                • 2011-07-26
                • 2015-03-15
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多