2009-11-04 2 views
1

Je suis en cours d'exécution sur plate-forme RHEL 5.1 64 bits à l'aide de gcc 4.1.2.Problème de définition NULL sur le système 64 bits

J'ai une fonction d'utilité:

void str_concat(char *buff, int buffSize, ...); 

qui concats char * dans liste variadique (...), alors que le dernier argument doit être NULL, pour désigner fin des arguments. Sur le système 64 bits, la valeur NULL est de 8 octets.

Maintenant, au problème. Mon application inclut directement/indirectement 2 fichiers stddef.h.

première est /usr/include/linux/stddef.h qui définit NULL comme suit:

#undef NULL 
#if defined(__cplusplus) 
#define NULL 0 
#else 
#define NULL ((void *)0) 
#endif 

La seconde est une /usr/lib/gcc/x86_64-redhat-Linux/4.1.2/include/stddef.h

#if defined (_STDDEF_H) || defined (__need_NULL) 
#undef NULL  /* in case <stdio.h> has defined it. */ 
#ifdef __GNUG__ 
#define NULL __null 
#else /* G++ */ 
#ifndef __cplusplus 
#define NULL ((void *)0) 
#else /* C++ */ 
#define NULL 0 
#endif /* C++ */ 
#endif /* G++ */ 
#endif /* NULL not defined and <stddef.h> or need NULL. */ 
#undef __need_NULL 

Bien sûr, je dois le 2ème, car il définit NULL comme __null (8 octets), tandis que le 1er un définit comme entier 0 (4 octets). Comment éviter que /usr/include/linux/stddef.h soit inclus de manière indirecte?

UPD:

  1. ligne de compilation est assez simple:

    g ++ -Wall -fmessage longueur = 0 -g -pthread

  2. Beaucoup d'entre vous a conseillé de passer (void *) 0. Cela va bien sûr fonctionner. Le problème que la fonction est utilisée dans beaucoup, je veux dire beaucoup d'endroits. Je voudrais trouver une solution qui me donnera ce que le standard C++ promet - NULL de 8 octets.

+0

Il serait utile si nous avions votre ligne de compilation, pour voir les répertoires dans le chemin d'inclusion. –

+0

Est-il possible de voir la sortie de l'assemblage générée? – Malkocoglu

+0

Je prétraiter le fichier (gcc -E) pour prouver que la valeur NULL est remplacée par 0. – dimba

Répondre

12

Il n'y a pas de "problème de définition NULL" dans ce cas. Il y a un problème avec la façon dont vous essayez d'utiliser NULL dans votre code.

NULL ne peut pas être transmis de manière portative aux fonctions variées en C/C++ par lui-même. Vous devez le lancer explicitement avant de passer, c'est-à-dire dans votre cas, vous devez passer (const char*) NULL comme terminateur de la liste d'arguments.

Votre question est balisée en C++. Dans tous les cas, indépendamment de la taille, en C++ NULL sera toujours défini comme un entier constante. Il est illégal en C++ de définir NULL comme un pointeur. Puisque votre fonction attend un pointeur (const char *), aucune définition de NULL ne fonctionnera jamais pour cela dans le code C++.

Pour un code plus propre, vous pouvez définir votre propre constante, comme

const char* const STR_TERM = NULL; 

et l'utiliser dans les appels à votre fonction. Mais vous ne serez jamais capable d'utiliser de manière significative seulement NULL à cette fin. Chaque fois qu'un simple NULL est passé en tant qu'argument variadique, il s'agit d'un bug de portabilité flagrante qui doit être corrigé.Votre mise à jour prétend que "la norme C++ promet NULL de taille de 8 octets" (sur une plate-forme 64 bits je présume). Cela n'a tout simplement aucun sens. La norme C++ ne promet rien de pareil à propos de NULL.

NULL est destiné à être utilisé comme valeur de référence. Il n'a pas de taille spécifique et il n'y a aucune utilisation valide de NULL où sa taille réelle pourrait même importer à distance.


Je cite ISO/CEI 14882: 1998, section 18.1 'types', paragraphe 4:

Le NULL macro est une implémentation définie null constante de pointeur C++ dans la présente Norme internationale (4.10). 180)

180) définitions possibles sont 0 et 0L, mais pas (void *) 0.

+1

Parfaitement acceptable pour passer un pointeur null non const - et plus court. –

+0

"La valeur NULL ne peut pas être transmise de manière portable aux fonctions variées en C/C++." - Non, ça ne peut pas être fait en C++. Je suis assez sûr que ça va bien en C. –

+1

@Chris Lutz: Non, ça ne va pas bien en C. Comment peux-tu savoir si tu passes "0", "0L", "(void *) 0" ou autre chose ? – AnT

0

J'ai déjà répondu avec la réponse ci-dessous. Mais ensuite j'ai vu que j'avais mal interprété plusieurs informations et donné une réponse incorrecte. Juste par curiosité, j'ai fait le même test avec VS2008 et j'ai obtenu des résultats différents. Ceci est juste exercice du cerveau ...

 
Why do you need the second one ? Both headers say the same thing. 
And it does not even matter if you write 0 or NULL or ((void *)0) 
All of them will take 8 bytes. 

J'ai fait un test rapide sur une plate-forme 64 bits avec GCC 4.1.3

#include <string.h> 

void str_concat(char *po_buf, int pi_max, ...) 
{ 
    strcpy(po_buf, "Malkocoglu"); /* bogus */ 
} 

int main() 
{ 
    char buf[100]; 
    str_concat(buf, 100, "abc", 1234LL, "def", 5678LL, "ghi", 2345LL, "jkl", 6789LL, "mno", 3456LL, 0, "pqx", 0); 
    return 1; 
} 

Et ceci est l'assemblage généré par le compilateur ...

main: 
.LFB3: 
    pushq %rbp 
.LCFI3: 
    movq %rsp, %rbp 
.LCFI4: 
    subq $192, %rsp 
.LCFI5: 
    leaq -112(%rbp), %rdi 
    movl $0, 64(%rsp)       0 
    movq $.LC2, 56(%rsp)      "pqx" 
    movl $0, 48(%rsp)       0 
    movq $3456, 40(%rsp)      3456LL 
    movq $.LC3, 32(%rsp)      "mno" 
    movq $6789, 24(%rsp)      6789LL 
    movq $.LC4, 16(%rsp)      "jkl" 
    movq $2345, 8(%rsp)      2345LL 
    movq $.LC5, (%rsp)       "ghi" 
    movl $5678, %r9d       5678LL 
    movl $.LC0, %r8d       "def" 
    movl $1234, %ecx       1234LL 
    movl $.LC1, %edx       "abc" 
    movl $100, %esi       100 
    movl $0, %eax 
    call str_concat 
    movl $1, %eax 
    leave 
    ret 

Remarque tous les déplacements de la pile sont 8 octets ...

 
Compiler treats 0 as it was a 32-bit data-type. 
Although it does the correct displacement on the 
stack pointer, the value pushed should not be 32-bit ! 

I ont fait le même test avec VS2008, la sortie du dispositif est le suivant:

mov QWORD PTR [rsp+112], 0 
lea rax, OFFSET FLAT:$SG3597 
mov QWORD PTR [rsp+104], rax 
mov QWORD PTR [rsp+96], 0 
mov QWORD PTR [rsp+88], 3456  ; 00000d80H 
lea rax, OFFSET FLAT:$SG3598 
mov QWORD PTR [rsp+80], rax 
mov QWORD PTR [rsp+72], 6789  ; 00001a85H 
lea rax, OFFSET FLAT:$SG3599 
mov QWORD PTR [rsp+64], rax 
mov QWORD PTR [rsp+56], 2345  ; 00000929H 
lea rax, OFFSET FLAT:$SG3600 
mov QWORD PTR [rsp+48], rax 
mov QWORD PTR [rsp+40], 5678  ; 0000162eH 
lea rax, OFFSET FLAT:$SG3601 
mov QWORD PTR [rsp+32], rax 
mov r9d, 1234    ; 000004d2H 
lea r8, OFFSET FLAT:$SG3602 
mov edx, 100    ; 00000064H 
lea rcx, QWORD PTR buf$[rsp] 
call [email protected]@YAXPEADHZZ   ; str_concat 

Ce compilateur temps génère un code différent et il traite 0 en tant que données de type 64-bit (notez le mot-clé QWORD). La valeur et le déplacement de la pile sont corrects. VS et GCC se comportent différemment.

+0

En fait, je me suis contenté d'utiliser 4 octets sur une plate-forme 64 bits, n'est-ce pas? –

+1

Cela dépend de system/compiler/ABI mais oui généralement int est de 4 octets mais 0 ne l'est pas. 0 est un nombre, ce n'est pas un type (comme int/long/char) et il est promu à tout ce que le compilateur pense qu'il devrait être ... – Malkocoglu

+0

Downvoted car 0 est * pas * 8 octets. Je suis tombé sur ceci avec un logiciel de Gnome dans les premiers jours de Gentoo AMD64. Dans une liste d'arguments variadiques, C ne sait pas convertir 0 en un pointeur, vous devez donc le convertir en void *. –

3

Retrait de la __GNUG__ cas, et inverser la ifdef/endif dans le second fichier, les deux fichiers faire:

#undef NULL 
#if defined(__cplusplus) 
#define NULL 0 
#else 
#define NULL ((void *)0) 
#endif 

Ce qui est de dire qu'ils définissent NULL comme ((void *) 0) pour les compilations C et 0 pour C++.

Donc la réponse simple est "Ne pas compiler en C++".

Votre vrai problème est votre désir d'utiliser NULL dans votre liste de variantes, combiné avec le dimensionnement des arguments imprévisibles de votre compilateur. Ce que vous pourriez essayer est d'écrire "(void *) 0" au lieu de NULL pour terminer votre liste, et forcer le compilateur à passer un pointeur de 8 octets au lieu d'un int de 4 octets.

+1

Puisqu'il s'agit d'une liste de valeurs 'const char *' qui est passée, il semble logique d'utiliser '(char *) 0 'plutôt que' (void *) 0 ', même si je suis d'accord que le résultat final est indiscernable. –

1

Vous ne pouvez pas être en mesure de réparer les inclusions car le système inclut un labyrinthe sinueux.

Vous pouvez résoudre le problème en utilisant (void *) 0 ou (char *) 0 au lieu de NULL. Après l'avoir considéré, je rejette mon idée précédente de redéfinir NULL. Ce serait une mauvaise chose à faire et pourrait gâcher beaucoup d'autres codes.

+2

Redéfinir NULL conduit à la folie plus grande que celle qui a causé la question à poser. –

5

Une solution - peut-être même le meilleur, mais certainement très fiable - est de passer un pointeur char null explicite à votre fonction appelle:

str_concat(buffer, sizeof(buffer), "str1", "str2", ..., (char *)0); 

ou:

str_concat(buffer, sizeof(buffer), "str1", "str2", ..., (char *)NULL); 

Ce standard recommandé pour la fonction execl() dans les systèmes POSIX, par exemple, et pour la même raison - les arguments de fin d'une liste d'arguments de longueur variable sont soumis aux promotions habituelles (char ou short to int; float to double), mais ne peuvent pas autrement b e type sûr.

C'est aussi pourquoi les praticiens C++ évitent généralement variable-length argument lists; ils ne sont pas sûrs de type.