13

我想声明一个可以从多个 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 下验证了这一点。-fmerge-all-constants即使在 gcc 中也不会发生链接器合并。

图表 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 标准的工作解决方案。

4

4 回答 4

3

不幸的是,在“经典”C(参考 C89/90)中没有标准的方法来实现这一点。在 C89/90 中,只要您坚持使用数组,您就只能使用您描述的两种方法,各有优缺点。

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

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

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

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

于 2012-07-09T22:13:26.430 回答
3

我认为你的分析有些错误。当您打印 的地址时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("%d\n", arr[0]); }
(gdb) print arr
$1 = <optimized out>

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

如果我添加printf("%p\n",arr);到 and 的开头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
于 2012-07-09T22:40:37.477 回答
2

您可以尝试的一件事:

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

现在该数组仍然存在于每个 *.o 对象中,但是当这些对象在程序中链接在一起时,GNUld会将它们缩减为仅一个公共数据块。

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

#ifndef __GNUC__
#define __attribute__(x)
#endif
于 2012-07-09T22:17:32.023 回答
0

使用selectany variable 属性并为您的数组提供外部链接(即不要声明它们static)。这会将数组值保留在标头中,以便可以正确内联,并且该selectany属性将告诉链接器任意选择一个定义作为真实定义并丢弃其他定义(因为它们都是相同的,没关系)。

例如:

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

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

于 2012-07-09T22:18:33.053 回答