我想声明一个可以从多个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]); }
在这种情况下,编译器可以在file1中用arr[0]
替换1
。但是,由于arr
被声明为static const
,因此它的内存在两个C文件中都是重复的。 AFAIK C标准要求两个文件中的阵列地址不同。我在Linux下通过打印地址验证了这一点。即使在gcc中使用-fmerge-all-constants
,也不会发生链接器合并。
图表2:
header.h:
extern const int arr[2];
file1.c:
#include "header.h"
void file1() { printf("%d\n", arr[0]); }
file2.c:
#include "header.h"
const int arr[2] = { 1, 2 };
int file2() { for (int i = 0; i < 2; i++) printf("%d\n", arr[i]); }
在这种情况下,不会发生内存重复,但arr[0]
没有内联。
我认为C标准定义的可见范围是有缺陷的。因此,Linux / gcc下违反C标准的工作解决方案是我可以接受的。
答案 0 :(得分:3)
遗憾的是,没有标准的方法来实现“经典”C(指C89 / 90)。在C89 / 90中,只要您坚持使用数组,就只能使用您描述的两种方法,各自有利有弊。
在C99,情况会更好。在C99中,您可以使用所谓的复合文字,即只需将arr
定义为头文件中的宏
#define arr ((const int []) { 1, 2 })
然后希望编译器将“内联”数组。 const
类型的复合文字与字符串文字的处理方式相同:程序中不同出现的相同文字可以由编译器合并到实际对象的一个实例中(如果编译器没有内联它)。
AFAIK,即使在非C99模式下,GCC编译器也支持复合文字作为扩展。
答案 1 :(得分:3)
我认为你的分析有点不对劲。当您打印arr
的地址时,强制编译器会保留两个副本。如果你不这样做,海湾合作委员会将删除这两份副本。
确定链接器具有和尚未消除的内容的更好方法是查看输出文件中的实际对象。在Linux下,nm
程序会告诉你这个。
如果我用'gcc(Ubuntu / Linaro 4.6.1-9ubuntu3)4.6.1'编译你的代码(图1):
gcc -std=c99 -g3 -O6 -fmerge-all-constants file1.c file2.c main.c
然后我使用nm -a a.out | grep '\<arr\>'
在符号表中查找它:
$ nm -a a.out|grep '\<arr\>'|wc -l
0
事实上,如果你试图在gdb
找到它,你什么都找不到:
(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>
编译器已经完全优化了它。
如果我将printf("%p\n",arr);
添加到file1()
和file2()
的开头并以相同的方式编译,则nm -a a.out|grep '\<arr\>'
会返回对arr
的两个引用:< / p>
$ nm -a a.out|grep '\<arr\>'|wc -l
2
$ nm -a a.out|grep '\<arr\>'
00000000004006c8 r arr
00000000004006d0 r arr
答案 2 :(得分:2)
您可以尝试一件事:
const int arr[2] __attribute__((weak)) = { 1, 2 };
现在数组仍然存在于每个* .o对象中,但是当这些对象在程序中链接在一起时,GNU ld
会将它们简化为一个常见的数据块。
如果您还没有这样的东西,可能需要一些常见的头文件:
#ifndef __GNUC__
#define __attribute__(x)
#endif
答案 3 :(得分:0)
使用selectany
variable attribute并为您的数组提供外部链接(即不要声明它们static
)。这会将数组值保留在标题中,以便可以正确内联,selectany
属性将告诉链接器任意选择其中一个定义为真实定义并丢弃其他定义(因为它们'一切都一样,没关系)。
例如:
const int arr[] __attribute__((selectany)) = {1, 2};
编辑:这显然只适用于Windows目标; weak
属性不能用于我对Cygwin的GCC进行的快速测试,因为它在结果数据段中生成了多个数组副本。