Un tableau de caractères peut-il être utilisé avec n’importe quel type de données?

La fonction malloc() renvoie un pointeur de type void* . Il alloue de la mémoire en octets en fonction de la valeur size_t transmise en tant qu’argument. L’affectation résultante est constituée d’octets bruts pouvant être utilisés avec tout type de données en C (sans transtypage).

Un tableau de type char déclaré dans une fonction renvoyant void * peut-il être utilisé avec tout type de données tel que l’allocation résultante de malloc ?

Par exemple,

 #include  void *Stat_Mem(); int main(void) { //size : 10 * sizeof(int) int buf[] = { 1,2,3,4,5,6,7,8,9,10 }; int *p = Stat_Mem(); memcpy(p, buf, sizeof(buf)); for (int n = 0; n < 10; n++) { printf("%d ", p[n]); } putchar('\n'); return 0; } void *Stat_Mem() { static char Array[128]; return Array; } 

Le type déclaré de l’object statique Array est char . Le type effectif de cet object est son type déclaré. Le type effectif d’un object statique ne peut pas être modifié, ainsi, pour le rest du programme, le type effectif de Array est char .

Si vous essayez d’accéder à la valeur d’un object avec un type incompatible ou non répertorié dans la liste 1 , le comportement est indéfini.

Votre code tente d’accéder à la valeur stockée de Array utilisant le type int . Ce type n’est pas compatible avec le type char et ne figure pas dans la liste des exceptions. Par conséquent, le comportement n’est pas défini lorsque vous lisez le tableau à l’aide du pointeur int p :

 printf("%d ", p[n]); 

1 (extrait de: ISO: CEI 9899: Expressions 7 de 201X 6.5)
Un object doit avoir sa valeur stockée accessible uniquement par une expression lvalue qui possède l’un des types suivants:
– un type compatible avec le type effectif de l’object,
– une version qualifiée d’un type compatible avec le type effectif de l’object,
– un type qui est le type signé ou non signé correspondant au type effectif de l’object,
– un type qui est le type signé ou non signé correspondant à une version qualifiée du type effectif de l’object,
– un type d’agrégat ou d’union qui inclut l’un des types mentionnés ci-dessus parmi ses membres (y compris, de manière récursive, un membre d’une union sous-agrégée ou confinée), ou
– un type de caractère.

Non, vous ne pouvez pas utiliser un tableau d’octets arbitraire pour un type arbitraire en raison de problèmes d’alignement possibles. La norme en 6.3.2.3 Conversions / Pointeurs (accentuer le mien) est la suivante:

Un pointeur sur un object ou un type incomplet peut être converti en un pointeur sur un autre object ou type incomplet. Si le pointeur résultant n’est pas correctement aligné pour le type pointé, le comportement est indéfini . Sinon, lorsqu’il sera reconverti, le résultat sera comparable au pointeur original.

En tant que caractère constituant la plus petite exigence en matière d’alignement, vous ne pouvez pas vous assurer que votre tableau de caractères sera correctement aligné pour tout autre type. C’est pourquoi malloc garantit qu’un tampon obtenu par malloc (même s’il s’agit d’un void * ) répond au plus grand nombre d’exigences d’alignement possible pour pouvoir accepter tout autre type.


je pense que

 union { char buf[128]; long long i; void * p; long double f; }; 

doit avoir un alignement correct pour tous les types, car il est compatible avec les plus grands types de base (tels que définis au 6.2.5 Types). Je suis à peu près sûr que cela fonctionnera pour toutes les implémentations courantes (gcc, clang, msvc, …) mais malheureusement, je n’ai trouvé aucune confirmation que la norme le permette. Essentiellement à cause de la règle de crénelage ssortingcte définie dans 6.5 Expression §7:

Un object doit avoir sa valeur stockée accessible uniquement par une expression lvalue qui possède l’un des types suivants:

  • un type compatible avec le type effectif de l’object,
  • une version qualifiée d’un type compatible avec le type effectif de l’object,
  • un type qui est le type signé ou non signé correspondant au type effectif de l’object,
  • un type qui est le type signé ou non signé correspondant à une version qualifiée du type effectif de l’object,
  • un type d’agrégat ou d’union qui inclut l’un des types susmentionnés parmi ses membres (y compris, de manière récurrente, un membre d’un syndicat sous-agrégé ou confiné), ou
  • un type de caractère.

Donc, à mon humble avis, il n’existe pas de méthode portable ni conforme aux normes pour créer un allocateur personnalisé n’utilisant pas malloc .

Si on lit le raisonnement de la norme C89, la seule raison pour laquelle il existe des règles de type aliasing est d’éviter de demander aux compilateurs de faire des “hypothèses de pseudonymes dans le pire des cas”. L’exemple donné était:

  int a; void f( double * b ) { a = 1; *b = 2.0; g(a); } 

Si programme crée un tableau “char” dans une union contenant quelque chose dont l’alignement conviendrait à tout type, prend l’adresse de celle-ci et n’accède jamais à la mémoire de cette structure, sauf par le biais du pointeur résultant, il n’y a aucune raison pour que les règles de repliement causer aucune difficulté.

Il est intéressant de noter que les auteurs de la norme ont reconnu qu’une mise en œuvre pouvait être à la fois conforme et inutile. voir la justification de C89 2.2.4.1:

Si une mise en œuvre déficiente pourrait probablement permettre de mettre en place un programme répondant à cette exigence, tout en restant néanmoins inutile, le Comité a estimé qu’une telle ingéniosité exigerait probablement plus de travail que de rendre quelque chose d’utile. Le comité est d’avis que les responsables de la mise en œuvre ne doivent pas interpréter les limites de traduction comme les valeurs de parameters câblés, mais plutôt comme un ensemble de critères permettant de juger de la mise en œuvre.

Bien que cette déclaration soit faite en ce qui concerne les limites de mise en œuvre, le seul moyen d’interpréter le C89 comme étant même compatible à distance avec les dialectes du C qui l’ont précédé est de le considérer comme s’appliquant également de manière plus large: la norme n’essaie pas de manière exhaustive spécifiez tout ce qu’un programme doit être capable de faire, mais repose sur le bon sens des rédacteurs de compilateurs.

L’utilisation d’un tableau de type caractère en tant que magasin de sauvegarde de tout type, en supposant que l’un d’entre eux garantisse la résolution des problèmes d’alignement, ne devrait pas causer de problèmes avec un compilateur non obtus. La norme n’obligeait pas les rédacteurs de compilateurs à autoriser de telles choses car ils ne voyaient aucune raison de s’attendre à ce qu’ils fassent autrement. Malheureusement, ils n’ont pas su prévoir le chemin que la langue suivrait au XXIe siècle.