2012-07-09 5 views
13

Quiero declarar un arreglo constante al que se puede acceder desde múltiples archivos C y cuyo contenido puede ser insertado por el compilador, sin duplicar la memoria en varias unidades de compilación. El rendimiento es crítico en mi aplicación.Arreglo constante C invisible sin duplicación de memoria

Anexo 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]); } 

En ese caso, el compilador puede reemplazar por arr[0]1 en archivo1. Sin embargo, dado que arr está declarado static const, su memoria está duplicada en ambos archivos C. AFAIK el estándar C requiere que las direcciones de matriz sean diferentes en ambos archivos. Lo he verificado en Linux imprimiendo las direcciones. No se produce consolidación de vinculador incluso con -fmerge-all-constants en gcc.

Anexo 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]); } 

En ese caso, no se produce la duplicación de memoria, pero no arr[0] se colocarán en línea.

Considero que el alcance de visibilidad definido por el estándar C es defectuoso. Como tal, una solución de trabajo bajo Linux/gcc que viola el estándar C es aceptable para mí.

+0

En realidad, 'file2' también permite inlining la matriz. 'gcc -O9' desenrolla el ciclo y empuja directamente los dos valores. – aschepler

+0

@aschepler: Sí. En mi sistema, con -O2, el compilador deja de alinearse una vez que establezco tres o más elementos en la matriz. –

Respuesta

3

Una cosa que usted puede intentar:

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

Ahora la matriz todavía existe en todos los * .o objeto, pero cuando esos objetos están unidos entre sí en un programa, GNU ld los reducirá a un solo trozo común de datos.

Si aún no dispone de una cosa así, es posible que desee en algún archivo cabecera común:

#ifndef __GNUC__ 
#define __attribute__(x) 
#endif 
+0

¡Confirmado! La alineación de trabajos y la dirección de la matriz es la misma en cada archivo . ¡Muchas gracias! –

4

Desafortunadamente, no hay una forma estándar de lograr eso en C "clásica" (refiriéndose a C89/90). En C89/90, usted está limitado a los dos enfoques que describió, con sus pros y sus contras respectivos, siempre y cuando insista en usar una matriz.

En C99 las cosas son mejores. En el C99 se puede utilizar los llamados literales compuestos, es decir, simplemente definen arr como una macro en el archivo de cabecera

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

y luego esperar que el compilador "en línea" de la matriz. Los literales compuestos de los tipos const se tratan de la misma manera que los literales de cadena: el compilador puede fusionar distintas apariciones de literal idéntico en el programa en una instancia del objeto real (si el compilador no lo alinea).

AFAIK, el compilador GCC admite literales compuestos como una extensión incluso en modos que no son C99.

+0

No pude hacer que tu método funcione. Obtengo valores diferentes (y no válidos) si imprimo la "dirección" de la matriz directamente. Si declaro una función en cada archivo que toma un puntero como argumento y paso el literal de la matriz a cada función, también obtengo valores de puntero diferentes pero válidos. El contenido de la matriz es correcto cuando lo imprimo en cada función. –

+0

También intenté imprimir directamente los valores de la matriz (sin pasar la dirección del puntero). En el desmontaje, veo que el compilador está almacenando todos los elementos de la matriz en la pila (mov 1, mov 2, etc.) antes de imprimir los valores. Gracias por la ayuda, no sabía acerca de los literales compuestos de matriz C99. –

+0

@Laurent Birtz: se supone que los literales compuestos generan objetos * locales * cuando se usan en el alcance de la función y objetos * static * cuando se usan en el alcance del archivo. Esto implica que los valores se almacenarán efectivamente "en la pila" desde el punto de vista abstracto. Sin embargo, espero que el compilador pueda optimizar esto, es decir, "alinear" los valores de la matriz y/o "fusionar" las distintas instancias en una sola. – AnT

1

Uso del selectanyvariable attribute y dar a sus matrices de vinculación externa (es decir, no los declaran static) . Esto mantendrá el valor de la matriz en el encabezado para que se pueda insertar correctamente, y el atributo selectany le dirá al enlazador que elija arbitrariamente una de las definiciones para que sea la real y descarte las demás (ya que todas son iguales , no importará).

Por ejemplo:

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

EDITAR: Al parecer, esto sólo funciona en objetivos Windows; el atributo weak no funcionó, en cambio, en una prueba rápida que hice con Cygwin's GCC, ya que produjo varias copias de la matriz en el segmento de datos resultante.

+0

gcc docs say: El atributo 'selectany' solo está disponible en los destinos de Microsoft Windows. – aschepler

+0

D'oh, tienes razón. Parece que el atributo 'weak' no funciona en una prueba rápida que hice: produjo varias copias de la matriz en el segmento de datos. –

+0

Es bueno saber que también es posible hacerlo en Windows. Gracias. –

4

Creo que su análisis es algo erróneo. Cuando imprime la dirección de arr, fuerza el compilador para mantener dos copias. GCC eliminará ambas copias si no haces esto.

Una forma mejor de determinar qué tiene el enlazador, y qué no, eliminar es mirar los objetos reales en el archivo de salida. En Linux, el programa nm le dirá esto.

Si puedo compilar el código (Anexo 1) con 'gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1':

gcc -std=c99 -g3 -O6 -fmerge-all-constants file1.c file2.c main.c 

Luego utilizo nm -a a.out | grep '\<arr\>' a buscar en la tabla de símbolos :

$ nm -a a.out|grep '\<arr\>'|wc -l 
0 

de hecho, si se intenta encontrar en gdb, no se encuentra nada:

(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> 

El compilador lo ha optimizado completamente.

Si añado printf("%p\n",arr); al principio de file1() y file2() y compilar la misma manera, a continuación, vuelve nm -a a.out|grep '\<arr\>' dos referencias a arr:

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

Tienes razón, ese gcc maneja este caso especial inteligentemente, hoy en día. Pero no está proponiendo una solución real al problema. En primer lugar, otros compiladores podrían tratar eso de manera completamente diferente. Entonces, las constantes que no puede imprimir son de uso bastante restringido. –

+1

@JensGustedt: OP quería una solución que funcionara para GCC y Linux. De hecho, cualquier tipo de creación es dependiente del compilador. Incluso declarar una función 'en línea' es solo una pista para el compilador; el estándar C99 no requiere que la función esté realmente en línea. Esta solución es más general de lo que le das crédito. Hacer referencia a los ** valores ** de la matriz, incluida la impresión de estos valores, optimizará aún más la matriz. Sin embargo, cuando pasa un puntero a un elemento, entonces la matriz realmente debe existir, y el compilador está obligado por las reglas C99 'static'. – sfstewman

+0

Sin intención de ofender. Es solo que existe una solución al problema. La respuesta de AndreyT da una, que funciona perfectamente con gcc, pero también funcionaría con otros compiladores conformes. Entonces, estás equivocado, "en línea" no es solo una "pista" sino simplemente un nombre inapropiado. Cambia las propiedades de visibilidad de la función. https://gustedt.wordpress.com/2010/11/29/myth-and-reality-about-inline-in-c99/ –