Ne pas lancer de pointeurs en C peut causer des problèmes?

Hier, j’étais en classe et à un moment donné, l’instructeur parlait du code C. Il a dit:

Quel est le but de faire un casting de pointeur en C? Le seul but est de faire en sorte que le compilateur interprète correctement les opérations de pointeur (par exemple, l’ajout d’un pointeur int entraînera un décalage différent de celui d’un pointeur de caractère). En dehors de cela, il n’y a pas de différence: tous les pointeurs sont représentés de la même manière en mémoire, que le pointeur pointe vers une valeur int, une valeur char, une valeur courte ou autre. Ainsi, lancer un pointeur ne modifiera rien dans la mémoire, il aidera simplement le programmeur à effectuer des opérations plus liées au type de pointeur qu’il traite.

Cependant, j’ai lu, spécialement ici dans Stack Overflow, que ce n’est pas vrai à 100%. J’ai lu que dans certaines machines étranges, les pointeurs de différents types peuvent être stockés de différentes manières en mémoire. Dans ce cas, le fait de ne pas modifier le type de pointeur peut entraîner des problèmes si le code est compilé sur ce type d’ordinateur.

En gros, c’est le genre de code dont je parle. Considérons le code ci-dessous:

int* int_pointer; char* char_pointer; int_pointer = malloc(sizeof(int)); *int_pointer = 4; 

Et maintenant deux options:

1.

 char_pointer = (char *)int_pointer; 

2

 char_pointer = int_pointer; 

Le code sur le cas 2 pourrait devenir un problème? Faire le casting (cas 1) changerait éventuellement le format du pointeur en mémoire (si oui, pourriez-vous donner un exemple de machine?)?

Merci

Ce que votre instructeur a dit à propos de tous les types de pointeurs partageant la même représentation est généralement vrai pour les implémentations réelles du langage C.

Cependant, ce n’est pas vrai du sharepoint vue du langage C abstrait. Le langage C garantit que

  1. char * pointeurs char * sont représentés de la même manière que les pointeurs void * .
  2. Les pointeurs sur une version qualifiée de type T ( const , volatile , ressortingct ) sont représentés de la même manière que les pointeurs sur T non qualifié.
  3. Les pointeurs sur tous les types de structure sont représentés de manière identique.
  4. Les pointeurs vers tous les types d’union sont représentés de manière identique.

C’est tout. Aucune autre garantie n’existe. Ce qui signifie qu’en ce qui concerne le langage lui-même, int * pointer a une représentation différente de double * pointeur. Et le pointeur sur la struct S est représenté différemment du pointeur sur l’ union U

Cependant, votre exemple avec char_pointer et int_pointer , tel que présenté, n’est pas exactement illustratif. char_pointer = int_pointer; L’affectation est simplement invalide (comme dans “ne comstack pas”). Le langage ne prend pas en charge les conversions implicites entre les types de pointeurs incompatibles. De telles conversions nécessitent toujours un opérateur de diffusion explicite.

Ne pas lancer de pointeurs en C peut causer des problèmes?

Il n’y a pas de conversion implicite entre les types de pointeur en C (comme par exemple entre les types arithmétiques) – à l’exception du type void * . Cela signifie que C nécessite une conversion si vous souhaitez convertir un pointeur vers un autre type de pointeur (sauf pour void * ). Si vous ne le faites pas, une implémentation est nécessaire pour émettre un message de diagnostic et est autorisée à faire échouer la traduction.

Certains compilateurs sont assez gentils (ou assez pervers) pour ne pas requérir le casting. Ils se comportent généralement comme si vous mettiez explicitement le casting.

  char *p = NULL; int *q = NULL; p = q; // not valid in C 

En résumé, vous devez toujours placer le cast lors de la conversion de pointeurs en pointeurs pour des raisons de portabilité.

MODIFIER:

En dehors de la portabilité, un exemple où l’absence de transtypage peut vous poser de vrais problèmes est par exemple avec des fonctions variadiques. Supposons une implémentation où la taille de char * est supérieure à la taille de int * . Disons que la fonction s’attend à ce que le type d’un argument soit char * . Si vous voulez passer un argument de int * , vous devez le convertir en char * . Si vous ne le lancez pas, lorsque la fonction accédera à l’object char * , certains bits de l’object auront une valeur indéterminée et le bevahior sera indéfini.

Printf est un exemple assez proche. Si un utilisateur ne parvient pas à transtyper void * l’argument du spécificateur de conversion p , C indique qu’il invoque un comportement non défini.

Ce que votre prof a dit semble juste.

… (par exemple, l’ajout d’un pointeur int entraînera un décalage différent de celui d’un pointeur de caractère)

Ceci est vrai parce que si int est de 4 octets et char est de 1 octet, append un 2 à un pointeur int aura pour effet de déplacer le pointeur de 8 octets, tandis que l’ajout d’un 2 à un pointeur de caractère ne fera que déplacer le pointeur vers l’octet suivant 2 positions éloignées.

En dehors de cela, il n’y a pas de différence: tous les pointeurs sont représentés de la même manière en mémoire, que le pointeur pointe vers une valeur int, une valeur char, une valeur courte ou autre.

C’est vrai, la machine ne fait pas de distinction entre les pointeurs char et int, c’est le problème du compilateur à traiter. Aussi comme votre prof a mentionné:

Le seul but est de faire en sorte que le compilateur interprète correctement les opérations de pointeur

Ainsi, lancer un pointeur ne modifiera rien dans la mémoire, il aidera simplement le programmeur à effectuer des opérations plus liées au type de pointeur qu’il traite.

C’est encore vrai. Le casting ne modifie pas la mémoire, il modifie simplement la façon dont une partie de la mémoire est interprétée.

Lancer un pointeur ne change pas la valeur du pointeur, il indique simplement au compilateur quoi faire avec le pointeur. Le pointeur lui-même est juste un nombre, et il n’a pas de type. C’est la variable où le pointeur est stocké qui a le type.

Il n’y a pas de différence entre vos deux manières d’affecter le pointeur (si le compilateur vous permet de faire l’assigment). Quel que soit le type de la variable de pointeur source, le pointeur sera utilisé en fonction du type de la variable de destination après l’affectation.

Vous ne pouvez tout simplement pas stocker un pointeur int dans une variable de pointeur de caractère. Une fois que la valeur est stockée dans la variable, elle n’a plus la moindre notion d’être un pointeur sur un int.

La diffusion des pointeurs est importante lorsque vous utilisez directement la valeur, par exemple:

 int* int_pointer; int_pointer = malloc(sizeof(int)); *int_pointer = 4; char c; c = *(char*)int_pointer; 

Lancer le pointeur signifie que le déréférencement lit un caractère à partir de l’emplacement, pas un entier.

La conversion de void * vers un type dont j’ai besoin dans mon programme (par exemple, int *, char *, struct x *) a un sens pour moi. Mais transtyper d’autres types, c’est-à-dire int * to char * etc., peut devenir problématique (du fait que les décalages sont différents pour les ints, les chars, etc.). Alors faites attention si vous le faites.

Mais … pour répondre spécifiquement à votre question, considérez les points suivants:

 int_pointer = malloc(2 * sizeof(int)); // 2 * 4 bytes on my system. *int_pointer = 44; *(int_pointer + 1 )= 55; printf("Value at int_pointer = %d\n", *int_pointer); // 44 printf("Value at int_pointer + 1 = %d\n", *(int_pointer + 1)); // 55 

(Les décalages ci-dessus seront par incréments de 4 octets.)

 //char_pointer = int_pointer; // Gives a warning ... char_pointer = (char*)int_pointer; // No warning. printf("Value at char_pointer = %d\n", *char_pointer); // 0 printf("Value at char_pointer = %d\n", *(char_pointer+1)); // 0 printf("Value at char_pointer = %d\n", *(char_pointer+2)); // 0 printf("Value at char_pointer = %d\n", *(char_pointer+3)); // 44 <== 

Quatre octets ont été affectés à la valeur int de 44. La représentation binary de 44 est 00101100 ... les 3 octets restants sont tous des 0. Ainsi, lors de l'access à l'aide de char * pointeur, nous incrémentons un octet à la fois et obtenons les valeurs ci-dessus.

Le casting pour les pointeurs est essentiel pour dire au compilateur comment effectuer une arithmétique de pointeur.

Par exemple, si x est un pointeur de caractère pointant vers la mémoire 2001, x ++ doit aller à 2002, mais si x est un pointeur entier x ++ doit être 2003.

Par conséquent, si le transtypage n’est pas effectué pour les pointeurs, le compilateur ne pourra pas effectuer correctement l’arithématique du pointeur.