web-dev-qa-db-fra.com

Est-il possible d'initialiser un pointeur C sur NULL?

J'avais écrit des choses comme

char *x=NULL;

en supposant que

 char *x=2;

créerait un pointeur char vers l'adresse 2.

Mais, dans Le GNU Didacticiel de programmation en C) il est indiqué que int *my_int_ptr = 2; enregistre la valeur entière 2 à l'adresse quelconque choisie aléatoirement dans my_int_ptr lorsque c'est alloué.

Cela semblerait impliquer que mon propre char *x=NULL assigne quelle que soit la valeur de NULL transtypée en un char correspond à une adresse aléatoire en mémoire.

Tandis que

#include <stdlib.h>
#include <stdio.h>

int main()
{
    char *x=NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

fait, en fait, imprimer

est NULL

quand je le compile et que je l'exécute, je crains de ne pouvoir compter que sur un comportement indéfini, ou du moins sous-spécifié, et d'écrire

char *x;
x=NULL;

au lieu.

87
fagricipni

Est-il possible d'initialiser un pointeur C sur NULL?

TL; DR Oui, beaucoup.


La réclamation réelle faite sur le guide se lit comme suit

Par contre, si vous utilisez uniquement la première affectation initiale, int *my_int_ptr = 2;, le programme essaiera de remplir le contenu de l'emplacement mémoire désigné par my_int_ptr avec la valeur 2. Puisque my_int_ptr est rempli de déchets, il peut s'agir de n'importe quelle adresse. [...]

Eh bien, ils sont faux, vous avez raison.

Pour l'instruction, ( ignore, pour le moment, le fait que la conversion de pointeur en entier est un comportement défini par la mise en oeuvre )

int * my_int_ptr = 2;

my_int_ptr est une variable (de type pointeur sur int), il a sa propre adresse (type: adresse du pointeur sur entier), vous enregistrez une valeur de 2 dans cette adresse .

Maintenant, my_int_ptr, étant un type de pointeur, on peut dire qu'il pointe vers la valeur de "type" à l'emplacement de mémoire indiqué by la valeur de my_int_ptr. Donc, vous attribuez essentiellement la valeur de la variable de pointeur, et non la valeur de l'emplacement mémoire indiqué par le pointeur.

Donc, pour conclure

 char *x=NULL;

initialise la variable de pointeur x à NULL, pas la valeur à l'adresse mémoire indiquée par le pointeur .

C'est le même que

 char *x;
 x = NULL;    

Expansion:

Maintenant, étant strictement conforme, une déclaration comme

 int * my_int_ptr = 2;

est illégale, car elle implique une violation de contrainte. Pour être clair,

  • my_int_ptr est une variable de pointeur de type int *
  • une constante entière, 2 a le type int, par définition.

et comme ce ne sont pas des types "compatibles", cette initialisation est invalide car elle enfreint les règles d’attribution simple, mentionnées au chapitre §6.5.16.1/P1, décrites dans réponse de Lundin .

Si vous êtes intéressé par la manière dont l'initialisation est liée à de simples contraintes d'affectation, citez C11, chapitre §7.7.9, P11

L'initialiseur d'un scalaire doit être une expression unique, éventuellement entre accolades. La valeur initiale de l'objet est celle de l'expression (après conversion); les mêmes contraintes de type et conversions que pour une affectation simple s'appliquent, le type du scalaire étant la version non qualifiée de son type déclaré.

110
Sourav Ghosh

Le tutoriel est faux. Dans ISO C, int *my_int_ptr = 2; est une erreur. Dans GNU C, cela signifie la même chose que int *my_int_ptr = (int *)2;. Ceci convertit l'entier 2 en une adresse mémoire, d'une manière déterminée par le compilateur.

Il ne tente pas de stocker quoi que ce soit à l'emplacement adressé par cette adresse (le cas échéant). Si vous continuez à écrire *my_int_ptr = 5;, il essaiera alors de stocker le numéro 5 à l'emplacement indiqué par cette adresse.

53
M.M

Pour clarifier pourquoi le didacticiel est incorrect, int *my_int_ptr = 2; est une "violation de contrainte", c'est un code qu'il n'est pas permis de compiler et le compilateur doit vous donner un diagnostic lorsqu'il le rencontre.

Selon 6.5.16.1 Affectation simple:

Contraintes

Un des éléments suivants doit contenir:

  • l'opérande gauche a un type arithmétique atomique, qualifié ou non qualifié, et le droit a un type arithmétique;
  • l'opérande de gauche a une version atomique, qualifiée ou non qualifiée d'une structure ou d'un type d'union compatible avec le type de la droite;
  • l'opérande gauche a un type de pointeur atomique, qualifié ou non qualifié, et (compte tenu du type que l'opérande gauche aurait après la conversion de lvalue), les deux opérandes sont des pointeurs vers des versions qualifiées ou non qualifiées de types compatibles, et le type pointé par la gauche a tout les qualificatifs du type indiqué par la droite;
  • l'opérande gauche a un type de pointeur atomique, qualifié ou non qualifié, et (compte tenu du type que l'opérande gauche aurait après la conversion de lvalue), un opérande est un pointeur sur un type d'objet, et l'autre est un pointeur sur une version qualifiée ou non qualifiée de void, et le type pointé par la gauche a tous les qualificateurs du type pointé par la droite;
  • l'opérande gauche est un pointeur atomique, qualifié ou non qualifié, et la droite est une constante de pointeur nulle; ou
  • l'opérande gauche a le type atomique, qualifié ou non qualifié, _Bool, et le droit est un pointeur.

Dans ce cas, l'opérande gauche est un pointeur non qualifié. Nulle part il n'est mentionné que l'opérande droit est autorisé à être un entier (type arithmétique). Donc, le code enfreint le standard C.

On sait que GCC se comporte mal, sauf si vous lui indiquez explicitement qu'il s'agit d'un compilateur C standard. Si vous compilez le code en tant que -std=c11 -pedantic-errors, le diagnostic sera correctement effectué comme il se doit.

17
Lundin

int *my_int_ptr = 2

stocke la valeur entière 2 dans n'importe quelle adresse aléatoire dans my_int_ptr lorsqu'elle est allouée.

C'est complètement faux. Si ceci est écrit, alors procurez-vous un meilleur livre ou tutoriel.

int *my_int_ptr = 2 définit un pointeur entier qui pointe vers l'adresse 2. Vous risquez probablement un blocage si vous tentez d'accéder à l'adresse 2.

*my_int_ptr = 2, c'est-à-dire sans int dans la ligne, stocke la valeur deux avec l'adresse quelconque choisie par my_int_ptr. Cela dit, vous pouvez affecter NULL à un pointeur lorsqu'il est défini. char *x=NULL; est parfaitement valide C.

Edit: Lors de l'écriture de ce texte, je ne savais pas que la conversion d'entier en pointeur était un comportement défini par l'implémentation. S'il vous plaît voir les bonnes réponses de @ M.M et @SouravGhosh pour plus de détails.

15
taskinoor

Beaucoup de confusion à propos des pointeurs C provient d'un très mauvais choix qui avait été fait à l'origine en ce qui concerne le style de codage, corroboré par un très mauvais choix dans la syntaxe du langage.

int *x = NULL; est correct, mais c’est très trompeur, je dirais même absurde, et cela a gêné la compréhension du langage pour beaucoup de novices. Cela donne à penser que plus tard, nous pourrions faire *x = NULL; ce qui est bien sûr impossible. Vous voyez, le type de la variable n'est pas int, et le nom de la variable n'est pas *x, ni le * dans la déclaration ne joue aucun rôle fonctionnel en collaboration avec le =. C'est purement déclaratif. Donc, ce qui a beaucoup plus de sens est le suivant:

int* x = NULL; qui correspond également à C, bien qu’il n’adhère pas au style de codage K & R original. Cela indique parfaitement que le type est int* et que la variable de pointeur est x. Il devient donc évident, même pour les non-initiés, que la valeur NULL est stockée dans x, qui est un pointeur sur int.

En outre, il est plus facile de dériver une règle: lorsque l’étoile est éloignée du nom de la variable, il s’agit d’une déclaration, tandis que l’étoile associée au nom est un pointeur de déréférencement.

Donc, maintenant, il devient beaucoup plus compréhensible que nous puissions faire plus bas x = NULL; ou *x = 2; autrement dit, il est plus facile pour un novice de voir comment variable = expression conduit à pointer-type variable = pointer-expression et dereferenced-pointer-variable = expression. (Pour les initiés, par "expression", je veux dire "rvalue".)

Le choix malheureux dans la syntaxe du langage est que lorsque vous déclarez des variables locales, vous pouvez dire int i, *p; qui déclare un entier et un pointeur sur un entier, ce qui laisse penser que le * est utile. une partie du nom. Mais ce n’est pas le cas, et cette syntaxe n’est qu’un cas particulier insolite, ajouté pour des raisons de commodité, et selon moi, elle n’aurait jamais dû exister, car elle invalide la règle que j’ai proposée plus haut. Autant que je sache, cette syntaxe n'a de sens dans le langage, mais même si c'est le cas, elle indique une différence dans la définition des types de pointeur en C. Partout ailleurs, dans les déclarations à variable unique, dans les listes de paramètres, dans les membres de structure, etc., vous pouvez déclarer vos pointeurs comme type* pointer-variable au lieu de type *pointer-variable; c'est parfaitement légal et plus logique.

14
Mike Nakis

J'aimerais ajouter quelque chose d'orthogonal aux nombreuses excellentes réponses. En fait, initialiser à NULL est loin d’être une mauvaise pratique et peut être pratique si ce pointeur peut ou ne peut pas être utilisé pour stocker un bloc de mémoire alloué de manière dynamique.

int * p = NULL;
...
if (...) {
    p = (int*) malloc(...);
    ...
}
...
free(p);

Puisque selon le norme ISO-CEI 9899free est un nop lorsque l'argument est NULL, le code ci-dessus (ou quelque chose de plus significatif du même ordre) est légitime.

6
Luca Citi

c'est un pointeur nul

int * nullPtr = (void*) 0;

C'est correct.

int main()
{
    char * x = NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

Cette fonction est correcte pour ce qu'elle fait. Il attribue l'adresse de 0 au pointeur de caractère x. C'est-à-dire qu'il pointe le pointeur x sur l'adresse de mémoire 0.

Alternative:

int main()
{
    char* x = 0;

    if ( !x )
        printf(" x points to NULL\n");

    return EXIT_SUCCESS;
}

Je suppose que ce que vous vouliez est:

int main()
{
    char* x = NULL;
    x = alloc( sizeof( char ));
    *x = '2';

    if ( *x == '2' )
        printf(" x points to an address/location that contains a '2' \n");

    return EXIT_SUCCESS;
}

x is the street address of a house. *x examines the contents of that house.
1
Vanderdecken