2009-05-13 4 views
30

Récemment, j'ai travaillé sur des périphériques embarqués, où nous avons des structures et des syndicats qui doivent être initialisés au moment de la compilation afin de pouvoir conserver certaines choses en mémoire flash ou en ROM qui n'ont pas besoin d'être modifiées. un petit flash ou SRAM à un peu d'un coût de performance. Actuellement le code compile comme C99 valide, mais sans cet ajustement il compilait également en tant que code C++, et ce serait bien de supporter aussi les choses compilées de cette façon. L'une des principales choses qui empêche cela est que nous utilisons des initialiseurs désignés C99 qui ne fonctionnent pas dans le sous-ensemble C de C++. Je ne suis pas un mordu du C++, donc je me demande comment il pourrait y avoir des choses simples dans C ou C++ compatible C++ qui permettent encore l'initialisation au moment de la compilation pour que les structures et les unions ne soient pas initialisé après le démarrage du programme dans SRAM.C++ Équivalent aux initialiseurs désignés?

Un point supplémentaire à noter: l'une des principales raisons de l'utilisation de l'initialiseur désigné est l'initialisation et non le premier membre d'une union. En outre, coller avec C++ standard ou ANSI C est un plus afin de maintenir la compatibilité avec d'autres compilateurs (je connais les extensions GNU qui fournissent quelque chose comme les initialiseurs désignés sans C99).

+1

Notez que les initialiseurs désignés travaillent maintenant en g ++. J'ai la version 4.8.1 et je pourrais utiliser des initialiseurs d'une énumération et cela a fonctionné comme prévu. –

Répondre

19

Je ne suis pas sûr que vous pouvez le faire en C++. Pour les choses que vous devez initialiser en utilisant initialiseurs désignés, vous pouvez mettre les séparément dans un fichier .c compilé comme C99, par exemple:

// In common header file 
typedef union my_union 
{ 
    int i; 
    float f; 
} my_union; 

extern const my_union g_var; 

// In file compiled as C99 
const my_union g_var = { .f = 3.14159f }; 

// Now any file that #include's the header can access g_var, and it will be 
// properly initialized at load time 
+3

+1 Marteaux pour clous, tournevis pour vis. –

+2

J'ai pris cela en compte, et je pourrais finir par suivre cette voie. Malheureusement, il y a une plate-forme que nous aimerions utiliser pour utiliser une bibliothèque C++ pour fournir un support perhipheral. Ils fournissent un accès à un compilateur en ligne, mais ils l'ont limité à un mode C++ uniquement (pour plus de simplicité). J'ai essayé de saisir la bibliothèque précompilée et d'utiliser une chaîne d'outils GCC locale, mais certains symboles ne sont pas résolus depuis leur bibliothèque (ils utilisent RealView de Keil/ARM) lors de la liaison avec GCC et Newlib. Je pourrais pré-compiler tout le code C99 localement et créer un lien en ligne. J'essaie juste de garder les choses simples :-) –

+7

Cela ne résout pas le problème en C++. –

1

Le code suivant compile sans problème avec g ++:

#include <iostream> 

struct foo 
{ 
    int a; 
    int b; 
    int c; 
}; 

union bar 
{ 
    int a; 
    float b; 
    long c; 
}; 

static foo s_foo1 = {1,2,3}; 
static foo s_foo2 = {1,2}; 
static bar s_bar1 = {42L}; 
static bar s_bar2 = {1078523331}; // 3.14 in float 


int main(int, char**) 
{ 
    std::cout << s_foo1.a << ", " << 
       s_foo1.b << ", " << 
       s_foo1.c << std::endl; 

    std::cout << s_foo2.a << ", " << 
       s_foo2.b << ", " << 
       s_foo2.c << std::endl; 

    std::cout << s_bar1.a << ", " << 
       s_bar1.b << ", " << 
       s_bar1.c << std::endl; 

    std::cout << s_bar2.a << ", " << 
       s_bar2.b << ", " << 
       s_bar2.c << std::endl; 

    return 0; 
} 

Voici le résultat:

$ g++ -o ./test ./test.cpp 
$ ./test 
1, 2, 3 
1, 2, 0 
42, 5.88545e-44, 42 
1078523331, 3.14, 1078523331 

la seule chose avec les initialiseurs du C est que vous devez initialiser tous les éléments de la structure ou le reste sera initiali zed avec des zéros. Vous ne pouvez pas choisir. Mais cela devrait toujours être OK pour votre cas d'utilisation. Un autre point à noter: une raison clé de l'utilisation de l'initialiseur désigné est l'initialisation comme PAS le premier membre d'une union. Pour cela, vous devez utiliser la "solution de contournement" illustrée dans l'exemple où j'ai défini le membre "float" en fournissant la valeur int équivalente. C'est un peu un hack, mais si cela résout votre problème.

+0

C'est uniquement parce que 42L est implicitement converti en nombre entier. Si vous vouliez initialiser le membre float à, par exemple, 3.5, vous ne pouvez pas le faire en C++. –

+0

Vous ne pouvez pas initialiser plus d'un membre d'une union.C'est une union après tout ;-) Cependant, si vous voulez initialiser la partie "float" vous devrez l'initialiser avec l'équivalent entier (probablement comme un nombre hexadécimal) – lothar

+6

Oui - mais les initialiseurs désignés par C99 vous permettent d'initialiser un élément de une union autre que la première sans avoir recours à des hacks tels que la détermination de l'équivalent entier défini par l'implémentation d'un flottant. –

0

rapport de trou sec:

Étant donné

struct S { 
    int mA; 
    int mB; 
    S() {} 
    S(int b} : mB(b) {} // a ctor that does partial initialization 
}; 

J'ai essayé dériver S1 de S, où le constructeur par défaut en ligne de S1 appelle S (int) et passe une valeur codée en dur ...

struct S1 { 
    S1() : S(22) {} 
} s1; 

... puis compilé avec gcc 4.0.1 -O2 -S. L'espoir était que l'optimiseur verrait que s1.mB serait nécessairement 22 et affecter la valeur lors de la compilation, mais de l'assembleur ...

movl $22, 4+_s1-"L00000000002$pb"(%ebx) 

... il semble que le code généré ne la initialisation à l'exécution avant la principale. Même si cela avait fonctionné, il serait difficilement compilable comme C99 et aurait le rôle de dériver une classe pour chaque objet que vous vouliez initialiser; Alors, ne vous embêtez pas.

+1

Merci. Il ne doit pas nécessairement compiler comme C99. Les initialiseurs sont dans un certain nombre d'endroits, mais ils sont définis en utilisant des macros, donc quelque chose où je peux ifdef __cplusplus avec une modification minime, c'est bien aussi. –

1
#ifdef __cplusplus 
struct Foo 
{ 
    Foo(int a, int b) : a(a), b(b) {} 
    int a; 
    int b; 
}; 

union Bar 
{ 
    Bar(int a) : a(a) {} 
    Bar(float b) : b(b) {} 
    int a; 
    float b; 
}; 

static Foo foo(1,2); 
static Bar bar1(1); 
static Bar bar2(1.234f); 
#else 
/* C99 stuff */ 
#endif // __cplusplus 

En C++, l'union peut également avoir des constructeurs. Peut être c'est ce que tu voulais?

+0

L'initialisation est-elle effectuée lors de l'exécution ou de la compilation? C'est, je suppose, la question critique ici. –

+0

Je vais devoir consulter la norme houx pour être sûr, mais juste de ma tête, je pense que toutes les variables statiques globales sont initialisées et stockées dans la section de données de l'image exécutable. Votre meilleur pari est de l'essayer avec votre compilateur et de voir ce qu'il fait. –

+1

Non, l'espace sera alloué dans l'image exécutable, initialisé à zéros et le constructeur appelé lors de l'exécution. Juste pour ajouter à l'amusement, cependant, les systèmes embarqués peuvent être un peu inconsistants sur ce dernier point - en théorie l'éditeur de liens recueille une liste d'appels constructeurs statiques puis libgcc les appelle pendant l'amorçage du processus, mais selon votre plate-forme arriver, ou seulement se produire si vous sélectionnez les bonnes options de construction. – Tom

2

C'est en quelque sorte une réponse et une question. Je réalise que ce fil est mort, mais c'est exactement ce que je regardais ce soir. J'ai fait quelques recherches et la chose la plus proche que je peux obtenir à ce que je veux (ce qui est similaire à ce que vous voulez ... J'ai travaillé avec des photos et je n'ai pas besoin d'utiliser C++, mais je suis curieux de savoir comment il pourrait être fait) est le premier exemple de code:

#include <iostream> 

using namespace std; 

extern "C" 
{ 
    typedef struct stuff 
    { 
     int x; 
     double y; 
    } things; 
} 

int main() 
{ 
    things jmcd = { jmcd.x = 12, jmcd.y = 10.1234 }; 
    cout << jmcd.x << " " << jmcd.y << endl; 
    return 0; 
} 

Cela a une apparence très similaire aux initialiseurs désignés de style C99 avec une mise en garde, je vais parler plus tard. (. Vous auriez probablement envelopper cette #ifdef __cplusplus si vous voulez que le struct à compiler soit) La deuxième version du code que je regardais est la suivante:

#include <iostream> 

using namespace std; 

extern "C" 
{ 
    typedef struct stuff 
    { 
     int x; 
     double y; 
    } things; 
} 


int main() 
{ 
    things jmcd; 
    jmcd.x = 12; 
    jmcd.y = 10.1234; 
    cout << jmcd.x << " " << jmcd.y << endl; 
    return 0; 
} 

En fait, de regarder le démontage, il apparaît le premier exemple est réellement plus lent. J'ai regardé la sortie de l'assemblée et, bien, je dois être un peu rouillé. Peut-être que quelqu'un pourrait me donner un aperçu. La sortie de l'assemblage de la première cpp compilé et ressemblait à:

main: 
.LFB957: 
    .cfi_startproc 
    .cfi_personality 0x0,__gxx_personality_v0 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    movl %esp, %ebp 
    .cfi_offset 5, -8 
    .cfi_def_cfa_register 5 
    subl $24, %esp 
    movl $0, 12(%esp) 
    movl $0, 16(%esp) 
    movl $0, 20(%esp) 
    movl $12, 12(%esp) 
    movl 12(%esp), %eax 
    movl %eax, 12(%esp) 
    fldl .LC0 
    fstpl 16(%esp) 
    fldl 16(%esp) 
    fstpl 16(%esp) 
    movl 12(%esp), %eax 
    movl %eax, 4(%esp) 
    fildl 4(%esp) 
    fldl 16(%esp) 
    faddp %st, %st(1) 
    fnstcw 2(%esp) 
    movzwl 2(%esp), %eax 
    movb $12, %ah 
    movw %ax, (%esp) 
    fldcw (%esp) 
    fistpl 4(%esp) 
    fldcw 2(%esp) 
    movl 4(%esp), %eax 
    leave 
    ret 
    .cfi_endproc 

Le deuxième exemple ressemblait à:

main: 
.LFB957: 
    .cfi_startproc 
    .cfi_personality 0x0,__gxx_personality_v0 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    movl %esp, %ebp 
    .cfi_offset 5, -8 
    .cfi_def_cfa_register 5 
    subl $24, %esp 
    movl $12, 12(%esp) 
    fldl .LC0 
    fstpl 16(%esp) 
    movl 12(%esp), %eax 
    movl %eax, 4(%esp) 
    fildl 4(%esp) 
    fldl 16(%esp) 
    faddp %st, %st(1) 
    fnstcw 2(%esp) 
    movzwl 2(%esp), %eax 
    movb $12, %ah 
    movw %ax, (%esp) 
    fldcw (%esp) 
    fistpl 4(%esp) 
    fldcw 2(%esp) 
    movl 4(%esp), %eax 
    leave 
    ret 
    .cfi_endproc 

Ces deux résultats ont été obtenus avec une commande g++ -O0 -S main.cpp. Clairement, l'exemple intuitivement moins efficace a généré un code d'opération plus efficace en termes de nombre d'instructions. D'autre part, il y a peu de cas où je pourrais imaginer que les quelques instructions soient critiques. (D'un autre côté, j'ai vraiment du mal à comprendre l'assemblage non écrit par les humains, alors peut-être qu'il me manque quelque chose ...) Je pense que cela apporte une solution, quoique tardive, à la question posée par James. La prochaine chose que je devrais tester est si la même initialisation est autorisée en C99; Si cela fonctionne, je pense que cela répond parfaitement au problème de James. Disclaimer: Je n'ai aucune idée si cela fonctionne ou se comporte de la même manière pour tous les autres compilateurs autres que g ++.

+0

Fonctionne pour moi - pas dans une section fast-path de toute façon. Meilleure solution que d'utiliser un autre fichier C! Upvote! – Sam

+5

Encore une fois avec être en retard, mais cela ne fonctionne tout simplement pas si vous permutez jmcd.x et jmcd.y dans la première solution. C'est parce que ce n'est pas une construction spéciale, c'est juste une initialisation régulière avec plus d'expressions. Donc jmcd.x = 12 est exécuté, et ensuite la valeur de résultat pour cette expression (12) est assignée au premier champ de la structure (x). La même chose pour y. Si vous les échangez, les deux champs seront 12. – Asaf

+0

La prise en charge des initialiseurs désignés en C++ est une extension GNU et ne fait pas partie de C++ 11. Même avec l'extension GCC, l'initialisation désignée n'est autorisée que pour les types POD. – chys

18

Miser sur la réponse de Shing Yip, et bénéficier du temps de 3 ans C++ 11 peut désormais garantir la compilation initialisation du temps:

union Bar 
{ 
    constexpr Bar(int a) : a_(a) {} 
    constexpr Bar(float b) : b_(b) {} 
    int a_; 
    float b_; 
}; 

constexpr Bar bar1(1); 
constexpr Bar bar2(1.234f);