Встроенный постоянный массив C без дублирования памяти
Я хочу объявить постоянный массив, к которому можно получить доступ из нескольких файлов 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]
на 1
в файле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.
Ответы
Ответ 1
Одна вещь, которую вы можете попробовать:
const int arr[2] __attribute__((weak)) = { 1, 2 };
Теперь массив все еще существует в каждом объекте *.o, но когда эти объекты связаны друг с другом в программе, GNU ld
уменьшит их до одного общего фрагмента данных.
Если у вас еще нет такой вещи, вы можете захотеть в каком-то общем заголовочном файле:
#ifndef __GNUC__
#define __attribute__(x)
#endif
Ответ 2
Нет стандартного способа достижения этого в "классическом" C (ссылаясь на C89/90), к сожалению. В C89/90 вы ограничены двумя описанными вами подходами, с их соответствующими плюсами и минусами, если вы настаиваете на использовании массива.
В C99 все будет лучше. В C99 вы можете использовать так называемые составные литералы, т.е. Просто определить arr
как макрос в файле заголовка
#define arr ((const int []) { 1, 2 })
а затем надеемся, что компилятор будет "встроить" массив. Компонентные литералы типов const
обрабатываются так же, как строковые литералы: разные вхождения идентичного литерала в программе могут быть объединены компилятором в один экземпляр фактического объекта (если компилятор не встраивает его).
AFAIK, компилятор GCC поддерживает составные литералы как расширение даже в режимах, отличных от C99.
Ответ 3
Я думаю, что ваш анализ несколько ошибочен. Когда вы печатаете адрес arr
, вы принудительно компилятор должен хранить две копии. GCC устранит обе копии, если вы этого не сделаете.
Лучший способ определить, что у компоновщика есть, а его нет, - это посмотреть на фактические объекты в выходном файле. В Linux программа nm
сообщит вам об этом.
Если я скомпилирую ваш код (экспонат 1) с помощью 'gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.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
:
$ nm -a a.out|grep '\<arr\>'|wc -l
2
$ nm -a a.out|grep '\<arr\>'
00000000004006c8 r arr
00000000004006d0 r arr
Ответ 4
Используйте атрибут переменной selectany
и дайте внешние привязки ваших массивов (т.е. не объявляйте их static
). Это сохранит значение массива в заголовке, чтобы он мог быть правильно вложен, а атрибут selectany
будет указывать компоновщику произвольно выбрать одно из определений, которое будет настоящим, и выбросить остальные (поскольку все они то же самое, это не имеет значения).
Например:
const int arr[] __attribute__((selectany)) = {1, 2};
EDIT. Это, по-видимому, работает только с объектами Windows; атрибут weak
не работал вместо этого в ходе быстрого теста, который я сделал с Cygwin GCC, поскольку он создал несколько копий массива в результирующем сегменте данных.