无内存复制的可内联常量C数组

无内存复制的可内联常量C数组,c,arrays,linker,constants,C,Arrays,Linker,Constants,我想声明一个常量数组,它可以从多个C文件访问,并且其内容可以由编译器内联,而不会在多个编译单元中复制内存。性能在我的应用程序中至关重要 附件1: header.h: static const int arr[2] = { 1, 2 }; file1.c: #include "header.h" void file1() { printf("%d\n", arr[0]); } file2.c: #include "header.h" int file2() { for (int i = 0;

我想声明一个常量数组,它可以从多个C文件访问,并且其内容可以由编译器内联,而不会在多个编译单元中复制内存。性能在我的应用程序中至关重要

附件1:

header.h:
static const int arr[2] = { 1, 2 };

file1.c:
#include "header.h"
void file1() { printf("%d\n", arr[0]); }

file2.c:
#include "header.h"
int file2() { for (int i = 0; i < 2; i++) printf("%d\n", arr[i]); }
在这种情况下,不会发生内存复制,但
arr[0]
不是内联的


我认为C标准定义的可见度范围有缺陷。因此,在Linux/gcc下,违反C标准的工作解决方案对我来说是可以接受的。

不幸的是,在“经典”C(指C89/90)中没有标准的方法来实现这一点。在C89/90中,只要您坚持使用阵列,您就只能使用您描述的两种方法,以及它们各自的优缺点

在C99中,情况更好。在C99中,您可以使用所谓的复合文字,即只需在头文件中将
arr
定义为宏

#define arr ((const int []) { 1, 2 })
然后希望编译器将数组“内联”。
const
类型的复合文字与字符串文字的处理方式相同:编译器可以将程序中不同出现的相同文字合并到实际对象的一个实例中(如果编译器没有内联它)


好吧,GCC编译器甚至在非C99模式下也支持将复合文本作为扩展。

您可以尝试一件事:

const int arr[2] __attribute__((weak)) = { 1, 2 };
现在,数组仍然存在于每个*.o对象中,但当这些对象在程序中链接在一起时,GNU
ld
会将它们减少为一个公共数据块

如果您还没有这样的东西,您可能需要在一些常见的头文件中:

#ifndef __GNUC__
#define __attribute__(x)
#endif

使用
选择any
并为您的阵列提供外部链接(即不要声明它们
静态
)。这将使数组值保留在标题中,以便可以正确内联,而
selectany
属性将告诉链接器任意选择其中一个定义为真实的定义,并丢弃其他定义(因为它们都是相同的,这不重要)

例如:

const int arr[] __attribute__((selectany)) = {1, 2};


编辑:这显然只适用于Windows目标;在我使用Cygwin的GCC进行的快速测试中,
弱属性不起作用,因为它在生成的数据段中生成了数组的多个副本。

我认为您的分析有点错误。打印
arr
的地址时,强制编译器保留两份副本。如果不这样做,GCC将消除这两个副本

确定链接器已删除和未删除的内容的更好方法是查看输出文件中的实际对象。在Linux下,
nm
程序将告诉您这一点

如果我用“gcc(Ubuntu/Linaro 4.6.1-9ubuntu3)4.6.1”编译你的代码(附件1):

然后我使用
nm-a a.out | grep'\'
在符号表中查找它:

$ nm -a a.out|grep '\<arr\>'|wc -l
0
编译器已经完全优化了它

如果我添加
printf(“%p\n”,arr)
file1()
file2()
的开头,并以相同的方式编译,然后
nm-a.out | grep'\'
返回对
arr
的两个引用:

$ nm -a a.out|grep '\<arr\>'|wc -l
2
$ nm -a a.out|grep '\<arr\>'
00000000004006c8 r arr
00000000004006d0 r arr
$nm-a a.out | grep'\'| wc-l
2.
$nm-a.out | grep'\'
0000000000 4006C8 r arr
0000000000 4006D0 r arr

gcc文档说:
selectany
属性仅在Microsoft Windows目标上可用。哦,你说得对。在我做的一个快速测试中,
weak
属性似乎不起作用——它在数据段中生成了数组的多个副本。很高兴知道在Windows上也可以这样做。谢谢。你说得对,现在gcc聪明地处理了这个特殊情况。但你并不是在为这个问题提出一个真正的解决方案。首先,其他编译器可能会完全不同地对待这一点。那么,不允许打印的常量的用途就相当有限了。@JensGustedt:OP想要一个适用于GCC和Linux的解决方案。事实上,任何类型的内联都依赖于编译器。即使声明函数
inline
,对编译器来说也只是一个提示;C99标准不要求函数实际上是内联的。这个解决方案比你想象的更普遍。引用数组的值,包括打印这些值,仍然会优化数组。但是,当您传递指向元素的指针时,数组必须实际存在,并且编译器受C99
static
规则的约束。无意冒犯。只是问题的解决方案存在。AndreyT的答案给出了一个例子,它可以完美地与gcc配合使用,但也可以与其他符合标准的编译器配合使用。那么,您就错了,
inline
不仅仅是一个“提示”,而是一个用词不当。它会更改函数的可见性属性。我确认你的信息。但是,如果文件中有一个对数组的不可内联引用,则仍然存在复制问题。我无法使您的方法正常工作。如果直接打印出数组的“地址”,则会得到不同的(无效)值。如果我在每个文件中声明一个以指针为参数的函数,并将数组文本传递给每个函数,我也会得到不同但有效的指针值。在每个函数中打印数组内容时,数组内容是正确的。我还尝试直接打印数组值(不传递指针地址)。在反汇编时,我看到编译器实际上是在打印出值之前将所有数组元素存储在堆栈上(mov 1、mov 2等)。不过,感谢您的帮助,我不知道C99数组复合文字。@Laurent Birtz:复合文字在函数范围内使用时应该生成本地对象,在文件范围内使用时应该生成静态对象。这意味着从抽象的角度来看,这些值确实将存储在“堆栈上”。但是,我希望编译器能够对此进行优化,即“内联”数组值和/或“内联”
$ nm -a a.out|grep '\<arr\>'|wc -l
0
(gdb) b file1
Breakpoint 1 at 0x400540: file /usr/include/x86_64-linux-gnu/bits/stdio2.h, line 105.
(gdb) r
Starting program: a.out 
Breakpoint 1, file1 () at file1.c:5
5   void file1() { printf("%d\n", arr[0]); }
(gdb) print arr
$1 = <optimized out>
$ nm -a a.out|grep '\<arr\>'|wc -l
2
$ nm -a a.out|grep '\<arr\>'
00000000004006c8 r arr
00000000004006d0 r arr