【问题标题】:Initialize array/block of data in C efficiently在 C 中有效地初始化数组/数据块
【发布时间】:2011-12-16 17:50:13
【问题描述】:

我试图在 C 中初始化一个数组,并且对于每个元素,GCC 正在生成一个 mov 指令(如果要初始化很多元素,这是一种低效的方法)。我将如何使用数组数据加载内存并从中返回指针而不是以这种方式初始化?

6:array.c       ****         int a[]={1,2,3,4,5,9};
26                      .loc 1 6 0
27 0008 C745E001        movl    $1, -32(%rbp)
27      000000
28 000f C745E402        movl    $2, -28(%rbp)
28      000000
29 0016 C745E803        movl    $3, -24(%rbp)
29      000000
30 001d C745EC04        movl    $4, -20(%rbp)
30      000000
31 0024 C745F005        movl    $5, -16(%rbp)
31      000000
32 002b C745F409        movl    $9, -12(%rbp)
32      000000

【问题讨论】:

  • 优化编译。
  • 当然,我对汇编不太熟悉,但假设它必须在运行时初始化,那么最佳方法是什么?
  • @Bwmat。那将被复制数组。您在函数中初始化空数组并将内容从外部数组复制到它。最快在运行时初始化数组。
  • @fazo:你确定这样更快吗?我不知道低级的东西,但否则我会猜到。 move 在代码中需要更多指令,但不需要从其他内存读取,也不需要循环。
  • @alertjean,我认为只需声明您的数组 const 就足够了。 gcc 非常适合优化不必要的存储。另一方面,如果您希望该数组可修改,则必须在某个时候存储该值。

标签: c arrays pointers gcc initialization


【解决方案1】:

我相信以下内容回答了您的问题“我将如何使用数组数据加载内存并从中返回指针?”:

int a_data[] = {1,2,3,4,5,9};

int main() {
  int *a = a_data;
}

这编译为:

        .data
a_data:
        .long   1
        .long   2
        .long   3
        .long   4
        .long   5
        .long   9

        .text
main:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movq    $a_data, -8(%rbp)
        leave
        ret
        .cfi_endproc

如您所见,值存在于数据段中,main() 只需获取指向数据的指针。

当然,如果你对a[]进行了变异,下次你取a_data的地址时,这些变异仍然存在。如果您希望获得原始值,您应该复制a_data,而不是简单地使用指向它的指针。

【讨论】:

  • 我认为最好有 a_dataconst 限定类型
  • @JensGustedt:这取决于 OP 想用它做什么。
  • 如果可以接受使用static 数据,我不明白为什么你必须有第二个变量。只需将 static 变量命名为 a 即可。
【解决方案2】:

我假设a 是一个局部变量,对吧?尝试将数组声明为静态 - 然后它的数据应该从文本块中加载。但是,变量的含义及其初始化将会改变。

【讨论】:

    【解决方案3】:

    根据您的需要,您有两个简单的选择:

    1) 静态化

    void bar(const int *);
    
    void foo() {
        static int a[]={1,2,3,4,5,9};
    
        bar(a);
    }
    

    Yields - 这只是对静态数据的引用。

    foo:
    .LFB0:
            .cfi_startproc
            movl    $a.1591, %edi
            jmp     bar
            .cfi_endproc
    .LFE0:
            .size   foo, .-foo
            .data
            .align 16
            .type   a.1591, @object
            .size   a.1591, 24
    a.1591:
            .long   1
            .long   2
            .long   3
            .long   4
            .long   5
            .long   9
    

    如果您需要非静态或恒定的数据,则另一种选择。是拥有静态数据,然后自己做一个 memcpy 将其移动到适当的位置。有趣的是,经过优化的 gcc 然后会使用各种策略将其复制到位。

    void bar(const int *);
    
    void foo() {
        static int s[]={1,2,3,4,5};
        int a[sizeof(s)/sizeof(s[0])];
        memcpy(a, s, sizeof(s));
    
        bar(a);
    }
    

    归根结底,代码是必要的,因为数据的内存位置在编译时并不固定,因此需要一些代码将数据复制到内存中。

    【讨论】:

      【解决方案4】:

      如果 a 是全局或静态文件或函数范围,则数据将存储在二进制映像中。当应用程序启动时,运行时系统会将值加载到内存中。

      int a[]={1,2,3,4,5,9}; 
      
      static int a[]={1,2,3,4,5,9};
      void func(void)
      {
        static int a[]={1,2,3,4,5,9};
      }
      

      如果 a 在函数级别声明为非静态:

      void func(void)
      {
        static int a[]={1,2,3,4,5,9};
      }
      

      这些值不需要存储在可执行文件中,而是一系列存储立即数到内存中(即在堆栈上)。

      无论哪种方式,您都无法避免在初始化数组时必须将值加载到内存中的事实。

      但是如果数组声明为

      const int a[] = {1,2,3,5,9};
      

      并且该单元是使用常量折叠编译的,那么该数组甚至可能永远不存在(取决于文件范围等)。

      【讨论】:

        猜你喜欢
        • 2013-01-25
        • 2020-10-25
        • 1970-01-01
        • 2015-08-09
        • 1970-01-01
        • 2021-01-12
        • 1970-01-01
        • 2010-12-18
        相关资源
        最近更新 更多