Подтвердить что ты не робот

Встроенный постоянный массив 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.

4b9b3361

Ответ 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, поскольку он создал несколько копий массива в результирующем сегменте данных.