不存在内存重复的可线性常数C阵列



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

附件1:

header.h:
static const int arr[2] = { 1, 2 };
file1.c:
#include "header.h"
void file1() { printf("%dn", arr[0]); }
file2.c:
#include "header.h"
int file2() { for (int i = 0; i < 2; i++) printf("%dn", arr[i]); }

在这种情况下,编译器可以用文件1中的1替换arr[0]。但是,由于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("%dn", arr[0]); }
file2.c:
#include "header.h"
const int arr[2] = { 1, 2 };
int file2() { for (int i = 0; i < 2; i++) printf("%dn", arr[i]); }

在这种情况下,不会发生内存复制,但arr[0]没有内联。

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

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

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

#define arr ((const int []) { 1, 2 })

然后希望编译器"内联"数组。const类型的复合文字与字符串文字的处理方式相同:编译器可以将程序中不同出现的相同文字合并到实际对象的一个实例中(如果编译器没有内联它)。

即使在非C99模式下,GCC编译器也支持将复合文字作为扩展。

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

确定链接器消除了什么和没有消除什么的更好方法是查看输出文件中的实际对象。在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("%dn", arr[0]); }
(gdb) print arr
$1 = <optimized out>

编译器已经完全优化了它。

如果我将printf("%pn",arr);添加到file1()file2()的开头,并以相同的方式编译它,那么nm -a a.out|grep '<arr>'将返回对arr的两个引用:

$ nm -a a.out|grep '<arr>'|wc -l
2
$ nm -a a.out|grep '<arr>'
00000000004006c8 r arr
00000000004006d0 r arr

您可以尝试一件事:

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

现在,数组仍然存在于每个*.o对象中,但当这些对象在程序中链接在一起时,GNU ld将把它们简化为一个公共数据块。

如果你还没有这样的东西,你可能想要一些常见的头文件:

#ifndef __GNUC__
#define __attribute__(x)
#endif

使用selectany变量属性并为数组提供外部链接(即不要声明它们为static)。这将把数组值保留在头中,这样它就可以正确地内联,selectany属性将告诉链接器任意选择其中一个定义作为真正的定义,并丢弃其他定义(因为它们都是一样的,所以没关系)。

例如:

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

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

最新更新