2012-07-09 5 views
13

Ich möchte ein konstantes Array deklarieren, auf das von mehreren C-Dateien zugegriffen werden kann und dessen Inhalt vom Compiler inline geschrieben werden kann, ohne den Speicher in mehreren Kompilierungseinheiten zu duplizieren. Leistung ist in meiner Anwendung von entscheidender Bedeutung.Inlinable konstantes C-Array ohne Speicherverdoppelung

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

In diesem Fall kann der Compiler arr[0] durch 1 in file1 ersetzen. Da jedoch arr als static const deklariert ist, ist sein Speicher in beiden C-Dateien dupliziert. AFAIK der C-Standard erfordert, dass die Array-Adressen in beiden Dateien unterschiedlich sind. Ich habe dies unter Linux durch Ausdrucken der Adressen verifiziert. Selbst mit -fmerge-all-constants in gcc tritt keine Linker-Konsolidierung auf.

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

In diesem Fall tritt kein Memory-Duplizierung aber arr[0] nicht inlined ist.

Ich betrachte den durch den C-Standard definierten Sichtbarkeitsbereich als fehlerhaft. Daher ist eine funktionierende Lösung unter Linux/gcc akzeptabel, die gegen den C-Standard verstößt.

+0

Eigentlich 'file2' ermöglicht auch die Anordnung inlining. 'gcc -O9 'entrollt die Schleife und drückt direkt die beiden Werte. – aschepler

+0

@ascheppler: Ja. Auf meinem System stoppt der Compiler mit -O2, sobald ich drei oder mehr Elemente im Array gesetzt habe. –

Antwort

3

Eine Sache, die Sie könnten versuchen:

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

Jetzt noch das Array in jedem existiert * .o Objekt, aber wenn diese Objekte zusammen in einem Programm verknüpft sind, GNU ld wird sich reduzieren, um nur ein gemeinsamer Brocken von Dateien.

Wenn Sie nicht bereits so etwas haben, können Sie in einem gemeinsamen Header-Datei möchten:

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

Bestätigt !! Inlining funktioniert und die Array-Adresse ist in jeder Datei identisch. Danke vielmals! –

4

Es gibt leider keine Standardmethode, um das in "klassischem" C (in Bezug auf C89/90) zu erreichen. In C89/90 sind Sie auf die beiden beschriebenen Ansätze mit ihren jeweiligen Vor- und Nachteilen beschränkt, solange Sie darauf bestehen, ein Array zu verwenden.

In C99 sind die Dinge besser. In C99 können Sie so Verbindung Literale genannt verwenden, das heißt definieren nur arr als Makro in der Header-Datei

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

und dann hoffen, dass der Compiler wird „inline“ das Array. Zusammengesetzte Literale der Typen const werden genauso behandelt wie Zeichenfolgenliterale: unterschiedliche Vorkommen identischen Literals im Programm können vom Compiler in eine Instanz des tatsächlichen Objekts zusammengeführt werden (wenn der Compiler sie nicht inline einfügt).

AFAIK, GCC-Compiler unterstützt zusammengesetzte Literale als Erweiterung auch in Nicht-C99-Modi.

+0

Ich konnte Ihre Methode nicht zum Laufen bringen. Ich bekomme andere (und ungültige) Werte, wenn ich die "Adresse" des Arrays direkt ausdrucke. Wenn ich in jeder Datei eine Funktion deklariere, die einen Zeiger als Argument akzeptiert und das Array-Literal an jede Funktion weitergibt, bekomme ich auch andere, aber gültige Zeigerwerte. Der Array-Inhalt ist korrekt, wenn ich ihn in jeder Funktion ausdrucke. –

+0

Ich habe auch versucht, die Array-Werte direkt auszudrucken (ohne die Zeigeradresse zu übergeben). Bei der Disassemblierung sehe ich, dass der Compiler tatsächlich alle Array-Elemente auf dem Stapel speichert (mov 1, mov 2 usw.), bevor die Werte ausgedruckt werden. Danke für die Hilfe, ich wusste nicht über C99 Array-Verbindungsliterale. –

+0

@Laurent Birtz: Zusammengesetzte Literale sollen * lokale * Objekte erzeugen, wenn sie im Funktionsbereich verwendet werden, und * statische * Objekte, wenn sie im Dateibereich verwendet werden. Dies bedeutet, dass die Werte tatsächlich vom abstrakten Standpunkt "auf dem Stapel" gespeichert werden. Ich erwarte jedoch, dass der Compiler dies optimieren kann, d. H. Die Array-Werte "inline" zu setzen und/oder die verschiedenen Instanzen zu einem zusammenzuführen. – AnT

1

Verwenden Sie die selectanyvariable attribute und Ihre Arrays externe Bindung geben (dh nicht erklären, sie static) . Dadurch wird der Array-Wert in der Kopfzeile beibehalten, sodass er ordnungsgemäß inline ausgerichtet werden kann, und das selectany-Attribut weist den Linker an, willkürlich eine der Definitionen als die echte auszuwählen und die anderen zu entfernen (da sie alle gleich sind) es wird nicht wichtig sein).

Zum Beispiel:

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

EDIT: Diese scheinbar funktioniert nur unter Windows Ziele; Das weak Attribut funktionierte stattdessen nicht auf einem schnellen Test, den ich mit Cygwins GCC tat, in dem es mehrfache Kopien des Feldes in dem resultierenden Datensegment produzierte.

+0

gcc docs say: Das Attribut 'selectany' ist nur für Microsoft Windows-Ziele verfügbar. – aschepler

+0

D'oh, du hast recht. Es sieht so aus, als ob das "schwache" Attribut nicht in einem schnellen Test funktioniert, den ich gemacht habe - es hat mehrere Kopien des Arrays im Datensegment erzeugt. –

+0

Gut zu wissen, dass es auch unter Windows möglich ist. Vielen Dank. –

4

Ich denke, dass Ihre Analyse etwas falsch ist. Wenn Sie die Adresse arr drucken, wenden Sie force den Compiler an, um zwei Kopien herum zu behalten. GCC wird beide Kopien entfernen, wenn Sie dies nicht tun.

Ein besserer Weg zu bestimmen, was der Linker hat und nicht eliminiert, ist, die tatsächlichen Objekte in der Ausgabedatei zu betrachten. Unter Linux wird Ihnen das nm Programm dies sagen.

Wenn ich kompilieren Sie Ihren Code (Abbildung 1) mit 'gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1':

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

ich dann nm -a a.out | grep '\<arr\>' verwenden es in der Symboltabelle zu sehen : wenn Sie

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

in der Tat, versuchen Sie es in gdb zu finden, finden Sie nichts:

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

Der Compiler hat es komplett optimiert.

Wenn ich hinzufügen printf("%p\n",arr); bis Anfang file1() und file2() und es ist die gleiche Art und Weise kompilieren, dann nm -a a.out|grep '\<arr\>' kehrt zwei Verweise auf arr:

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

Sie haben Recht, dass gcc diesen Spezialfall heute geschickt handhabt. Aber Sie schlagen keine wirkliche Lösung für das Problem vor. Die ersten anderen Compiler könnten das völlig anders behandeln. Dann sind Konstanten, die Sie nicht drucken dürfen, ziemlich eingeschränkt verwendbar. –

+1

@JensGustedt: Das OP wollte eine Lösung, die für GCC und Linux funktioniert. In der Tat ist jede Art von Inlining compilerabhängig. Eine Funktion 'inline' zu ​​deklarieren ist nur ein Hinweis für den Compiler; Der C99-Standard erfordert nicht, dass die Funktion tatsächlich inline ist. Diese Lösung ist allgemeiner als Sie es Kredit geben. Mit Bezug auf die ** Werte ** des Arrays, einschließlich des Druckens dieser Werte, wird das Array weiterhin optimiert. Wenn Sie jedoch einen Zeiger auf ein Element übergeben, muss das Array tatsächlich existieren, und der Compiler ist an die C99 'static'-Regeln gebunden. – sfstewman

+0

Nichts für ungut beabsichtigt. Es gibt nur eine Lösung für das Problem. AndreyTs Antwort gibt eine, die perfekt mit gcc funktioniert, aber auch mit anderen kompilierenden Compilern arbeiten würde. Dann irren Sie sich, "Inline" ist kein "Hinweis", sondern nur eine falsche Bezeichnung. Es ändert die Sichtbarkeitseigenschaften der Funktion. https://gustedt.wordpress.com/2010/11/29/myth-and-reality-about-inline-in-c99/ –

Verwandte Themen