32 votes

strcpy()/strncpy() se plante sur un membre de la structure avec un espace supplémentaire lorsque l'optimisation est activée sur Unix ?

Lors de la rédaction d'un projet, j'ai rencontré un problème étrange.

Voici le code minimal que j'ai réussi à écrire pour recréer le problème. Je stocke intentionnellement une chaîne réelle à la place de quelque chose d'autre, avec suffisamment d'espace alloué.

// #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h> // For offsetof()

typedef struct _pack{
    // The type of `c` doesn't matter as long as it's inside of a struct.
    int64_t c;
} pack;

int main(){
    pack *p;
    char str[9] = "aaaaaaaa"; // Input
    size_t len = offsetof(pack, c) + (strlen(str) + 1);
    p = malloc(len);
    // Version 1: crash
        strcpy((char*)&(p->c), str);
    // Version 2: crash
        strncpy((char*)&(p->c), str, strlen(str)+1);
    // Version 3: works!
        memcpy((char*)&(p->c), str, strlen(str)+1);
    // puts((char*)&(p->c));
    free(p);
  return 0;
}

Le code ci-dessus me perturbe :

  • Avec gcc/clang -O0 les deux strcpy() y memcpy() fonctionne sur Linux/WSL, et le puts() ci-dessous donne ce que j'ai entré.
  • Avec clang -O0 sur OSX le code se bloque avec strcpy() .
  • Avec gcc/clang -O2 o -O3 sur Ubuntu/Fedora/WSL le code crashs ( !!) à l'adresse strcpy() alors que memcpy() fonctionne bien.
  • Avec gcc.exe sous Windows, le code fonctionne bien quel que soit le niveau d'optimisation.

J'ai aussi trouvé d'autres traits du code :

  • (On dirait) l'entrée minimale pour reproduire le crash est de 9 octets (y compris le terminateur zéro), ou 1+sizeof(p->c) . Avec cette longueur (ou plus) un crash est garanti (Cher moi ...).

  • Même si j'alloue de l'espace supplémentaire (jusqu'à 1Mo) dans la base de données de l'UE. malloc() cela ne sert à rien. Les comportements ci-dessus ne changent pas du tout.

  • strncpy() se comporte exactement de la même manière, même avec la longueur correcte fournie à son 3ème argument.

  • Le pointeur ne semble pas avoir d'importance. Si le membre de la structure char *c est transformé en long long c (ou int64_t ), le comportement reste le même. (Mise à jour : déjà modifié).

  • Le message d'accident n'a pas l'air régulier. Il contient beaucoup d'informations supplémentaires.

    crash

J'ai essayé tous ces compilateurs et ils n'ont fait aucune différence :

  • GCC 5.4.0 (Ubuntu/Fedora/OS X/WSL, tous sont 64-bit)
  • GCC 6.3.0 (Ubuntu uniquement)
  • GCC 7.2.0 (Android, norepro ???) (Il s'agit du GCC de C4droid )
  • Clang 5.0.0 (Ubuntu/OS X)
  • MinGW GCC 6.3.0 (Windows 7/10, les deux x64)

En outre, cette fonction de copie de chaîne personnalisée, qui ressemble exactement à la fonction standard, fonctionne bien avec toutes les configurations de compilateur mentionnées ci-dessus :

char* my_strcpy(char *d, const char* s){
    char *r = d;
    while (*s){
        *(d++) = *(s++);
    }
    *d = '\0';
    return r;
}

Questions :

  • Pourquoi est-ce que strcpy() échouer ? Comment le faire ?
  • Pourquoi n'échoue-t-il que si l'optimisation est activée ?
  • Pourquoi est-ce que memcpy() échouent indépendamment de -O niveau ? ??

*Si vous voulez discuter de la violation de l'accès des membres de la structure, s'il vous plaît, allez-y. aquí .


Partie de objdump -d La sortie d'un exécutable en panne (sur WSL) :

objdump


P.S. Au départ, je veux écrire une structure dont le dernier élément est un pointeur vers un espace alloué dynamiquement (pour une chaîne de caractères). Lorsque j'écris la structure dans un fichier, je ne peux pas écrire le pointeur. Je dois écrire la chaîne de caractères réelle. J'ai donc trouvé cette solution : forcer le stockage d'une chaîne de caractères à la place d'un pointeur.

Ne vous plaignez pas non plus de gets() . Je ne l'utilise pas dans mon projet, mais seulement dans le code d'exemple ci-dessus.

1 votes

Les commentaires ne sont pas destinés à une discussion approfondie ; cette conversation a été déplacé vers le chat .

4 votes

La fermeture de cette question étant "trop large" est injustifiée à mes yeux, je vote pour la réouverture. Il manque toujours une réponse, qui discute en détail de la question de savoir si et pourquoi le comportement de gcc est conforme aux standards ou non.

2 votes

@Ctx je suis d'accord. C'est très intéressant. Il devrait être réouvert.

30voto

Stargateur Points 8393

Ce que vous faites est un comportement indéfini.

Le compilateur est autorisé à supposer que vous n'utiliserez jamais plus que sizeof int64_t pour la variable membre int64_t c . Donc si vous essayez d'écrire plus que sizeof int64_t (alias sizeof c ) sur c vous aurez un problème de dépassement de limites dans votre code. C'est le cas parce que sizeof "aaaaaaaa" > sizeof int64_t .

Le fait est que, même si vous allouez la taille correcte de la mémoire en utilisant malloc() le compilateur est autorisé à supposer que vous n'utiliserez jamais plus que sizeof int64_t dans votre strcpy() o memcpy() appel. Parce que vous envoyez l'adresse de c (alias int64_t c ).

TL;DR : Vous essayez de copier 9 octets dans un type composé de 8 octets (nous supposons qu'un octet est un octet). (De @Kcvin )

Si vous voulez quelque chose de similaire, utilisez les membres de tableaux flexibles de C99 :

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

typedef struct {
  size_t size;
  char str[];
} string;

int main(void) {
  char str[] = "aaaaaaaa";
  size_t len_str = strlen(str);
  string *p = malloc(sizeof *p + len_str + 1);
  if (!p) {
    return 1;
  }
  p->size = len_str;
  strcpy(p->str, str);
  puts(p->str);
  strncpy(p->str, str, len_str + 1);
  puts(p->str);
  memcpy(p->str, str, len_str + 1);
  puts(p->str);
  free(p);
}

Note : Pour un devis standard, veuillez vous référer à este réponse.

2 votes

Je trouverais cette réponse plus utile si vous consacriez quelques phrases supplémentaires sur pourquoi C'est UB.

0 votes

Les membres du tableau flexible ont été créés exactement pour ce cas d'utilisation. Si votre compilateur ne les prend pas en charge, vous pouvez les émuler en écrivant simplement la chaîne sur le disque immédiatement après la structure. Lorsque vous la relirez plus tard en mémoire, veillez à mettre à jour le pointeur à l'intérieur de la structure.

3 votes

La norme précise qu'un pointeur vers le membre initial d'une structure, convenablement converti, pointe vers la structure. Si un pointeur vers une allocation plus grande qu'une structure est converti en ce type de structure, puis en un type de caractère, le pointeur résultant peut être utilisé pour accéder à l'allocation sans être limité à la taille de la structure (entre autres choses, cela permet de traiter les allocations comme des tableaux de structures). La seule façon dont je peux voir que le comportement serait justifiable serait si (char*)(&p->c) n'est pas la même chose que (char*)(struct pack_*)(&p->c) .

16voto

Ayra Faceless Points 214

J'ai reproduit ce problème sur mon Ubuntu 16.10 et j'ai trouvé quelque chose d'intéressant.

Lorsqu'il est compilé avec gcc -O3 -o ./test ./test.c le programme se plantera si l'entrée est plus longue que 8 octets.

Après quelques manipulations, j'ai découvert que GCC remplaçait strcpy con memcpy_chk voyez ça.

// decompile from IDA
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int *v3; // rbx
  int v4; // edx
  unsigned int v5; // eax
  signed __int64 v6; // rbx
  char *v7; // rax
  void *v8; // r12
  const char *v9; // rax
  __int64 _0; // [rsp+0h] [rbp+0h]
  unsigned __int64 vars408; // [rsp+408h] [rbp+408h]

  vars408 = __readfsqword(0x28u);
  v3 = (int *)&_0;
  gets(&_0, argv, envp);
  do
  {
    v4 = *v3;
    ++v3;
    v5 = ~v4 & (v4 - 16843009) & 0x80808080;
  }
  while ( !v5 );
  if ( !((unsigned __int16)~(_WORD)v4 & (unsigned __int16)(v4 - 257) & 0x8080) )
    v5 >>= 16;
  if ( !((unsigned __int16)~(_WORD)v4 & (unsigned __int16)(v4 - 257) & 0x8080) )
    v3 = (int *)((char *)v3 + 2);
  v6 = (char *)v3 - __CFADD__((_BYTE)v5, (_BYTE)v5) - 3 - (char *)&_0; // strlen
  v7 = (char *)malloc(v6 + 9);
  v8 = v7;
  v9 = (const char *)_memcpy_chk(v7 + 8, &_0, v6 + 1, 8LL); // Forth argument is 8!!
  puts(v9);
  free(v8);
  return 0;
}

Votre pack struct fait croire à GCC que l'élément c a une longueur d'exactement 8 octets.

Et memcpy_chk échouera si la longueur de la copie est plus grande que le quatrième argument !

Il y a donc 2 solutions :

  • Modifiez votre structure

  • Utilisation des options de compilation -D_FORTIFY_SOURCE=0 (aime gcc test.c -O3 -D_FORTIFY_SOURCE=0 -o ./test ) pour désactiver les fonctions de fortification.

    Attention : Cela désactivera complètement la vérification du dépassement de tampon dans l'ensemble du programme ! !!

0 votes

Les commentaires ne sont pas destinés à une discussion approfondie ; cette conversation a été déplacé vers le chat .

10voto

Matt McNabb Points 14273

Aucune réponse n'a encore parlé en détail de la raison pour laquelle ce code peut être ou non un comportement non défini.

La norme n'est pas assez précise dans ce domaine, et une proposition est en cours pour y remédier. Dans le cadre de cette proposition, ce code ne serait PAS un comportement non défini, et les compilateurs générant du code qui se plante ne respecteraient pas la norme mise à jour. (Je reviens sur ce point dans mon paragraphe de conclusion ci-dessous).

Mais notez que d'après la discussion de -D_FORTIFY_SOURCE=2 dans d'autres réponses, il semble que ce comportement soit intentionnel de la part des développeurs concernés.


Je vais parler en me basant sur l'extrait suivant :

char *x = malloc(9);
pack *y = (pack *)x;
char *z = (char *)&y->c;
char *w = (char *)y;

Maintenant, les trois x z w font référence au même emplacement mémoire, et auraient la même valeur et la même représentation. Mais le compilateur traite z différemment pour x . (Le compilateur traite également w différemment à l'un de ces deux, bien que nous ne sachions pas lequel puisque le PO n'a pas exploré ce cas).

Ce sujet est appelé provenance des pointeurs . Il s'agit de la restriction sur l'objet sur lequel la valeur d'un pointeur peut s'étendre. Le compilateur prend z comme ayant une provenance uniquement sur y->c alors que x a une provenance sur la totalité de l'allocation de 9 octets.


La norme C actuelle ne spécifie pas très bien la provenance. Les règles telles que la soustraction de pointeurs ne peut se faire qu'entre deux pointeurs sur le même objet de tableau est un exemple de règle de provenance. Une autre règle de provenance est celle qui s'applique au code dont nous discutons, C 6.5.6/8 :

Lorsqu'une expression de type entier est ajoutée ou soustraite à un pointeur, le résultat a le type de l'opérande du pointeur. Si l'opérande du pointeur pointe sur un élément d'un tableau, et que le tableau est suffisamment grand, le résultat pointe sur un élément décalé par rapport à l'élément d'origine de telle sorte que la différence entre les indices des éléments du tableau d'origine et du résultat soit égale à l'expression entière. En d'autres termes, si l'expression P pointe vers le i -d'un objet tableau, les expressions (P)+N (de manière équivalente, N+(P) ) et (P)-N (où N a la valeur n ) pointent, respectivement, vers le i+n - et in -de l'objet tableau, à condition qu'ils existent. De plus, si l'expression P pointe sur le dernier élément d'un objet tableau, l'expression (P)+1 pointe une fois le dernier élément de l'objet tableau, et si l'expression Q pointe un élément après le dernier élément d'un tableau, l'expression (Q)-1 pointe vers le dernier élément de l'objet tableau. Si l'opérande pointeur et le résultat pointent tous deux vers des éléments du même objet tableau, ou vers un élément situé après le dernier élément de l'objet tableau, l'évaluation ne doit pas produire de dépassement de capacité ; sinon, le comportement est indéfini. Si le résultat pointe sur l'avant-dernier élément de l'objet tableau, il ne doit pas être utilisé comme opérande d'une fonction unaire de type * qui est évalué.

La justification du contrôle des limites de strcpy , memcpy revient toujours à cette règle : ces fonctions sont définies pour se comporter comme s'il s'agissait d'une série d'affectations de caractères à partir d'un pointeur de base qui est incrémenté pour atteindre le caractère suivant, et l'incrémentation d'un pointeur est couverte par la règle suivante (P)+1 comme indiqué dans cette règle.

Notez que le terme "l'objet tableau" peut s'appliquer à un objet qui n'a pas été déclaré comme un tableau. Cela est expliqué en détail dans la section 6.5.6/7 :

Pour les besoins de ces opérateurs, un pointeur vers un objet qui n'est pas un élément d'un tableau se comporte de la même manière qu'un pointeur vers le premier élément d'un tableau de longueur un avec le type de l'objet comme type d'élément.


La grande question ici est : qu'est-ce que "l'objet tableau" ? ? Dans ce code, est-ce que y->c , *y ou l'objet réel de 9 octets renvoyé par malloc ?

Il est important de noter que la norme n'apporte aucun éclairage sur cette question. Chaque fois que nous avons des objets avec des sous-objets, la norme ne dit pas si 6.5.6/8 se réfère à l'objet ou au sous-objet.

Un autre facteur de complication est que la norme ne fournit pas de définition du terme "tableau". ni pour "array object". Mais pour faire court, l'objet alloué par malloc est décrit comme "un tableau" à divers endroits dans la norme, il semble donc que l'objet de 9 octets soit un candidat valide pour "l'objet tableau". (En fait, il s'agit de l'objet seulement ce candidat pour le cas de l'utilisation x pour itérer sur l'allocation de 9 octets, ce qui, je pense que tout le monde est d'accord, est légal).


Note : cette section est très spéculative et je tente de fournir un argument pour expliquer pourquoi la solution choisie par les compilateurs n'est pas cohérente.

Un argument podría être fait que &y->c signifie que la provenance est le int64_t sous-objet. Mais cela entraîne immédiatement des difficultés. Par exemple, est-ce que y ont la provenance de *y ? Si c'est le cas, (char *)y devrait avoir la provenance *y encore, mais cela contredit la règle de 6.3.2.3/7 qui veut que le casting d'un pointeur vers un autre type et son retour renvoie le pointeur original (tant que l'alignement n'est pas violé).

Une autre chose qu'il ne couvre pas est le chevauchement des provenances. Un pointeur peut-il être comparé de manière inégale à un pointeur de même valeur mais de provenance plus petite (qui est un sous-ensemble de la provenance plus grande) ?

De plus, si nous appliquons ce même principe au cas où le sous-objet est un tableau :

char arr[2][2];
char *r = (char *)arr;    
++r; ++r; ++r;     // undefined behavior - exceeds bounds of arr[0]

arr est défini comme signifiant &arr[0] dans ce contexte, donc si la provenance de &X es X alors r est en fait limité à la première ligne du tableau - un résultat peut-être surprenant.

Il serait possible de dire que char *r = (char *)arr; mène à UB ici, mais char *r = (char *)&arr; ne le fait pas. En fait, j'avais l'habitude de promouvoir ce point de vue dans mes messages il y a de nombreuses années. Mais je ne le fais plus : d'après mon expérience de la défense de cette position, il est tout simplement impossible de la rendre cohérente, il y a trop de scénarios problématiques. Et même si elle pouvait être rendue auto-consistante, le fait est que la norme ne le spécifie pas. Au mieux, ce point de vue devrait avoir le statut d'une proposition.


Pour finir, je vous recommande de lire N2090 : Clarification de la provenance des pointeurs (Projet de rapport de défaut ou proposition pour C2x) .

Leur proposition est que la provenance s'applique toujours à une allocation . Cela rend caduques toutes les subtilités des objets et des sous-objets. Il n'y a pas de sous-allocations. Dans cette proposition, tous les x z w sont identiques et peuvent être utilisées pour couvrir la totalité de l'allocation de 9 octets. À mon avis, la simplicité de cette méthode est attrayante, comparée à ce qui a été discuté dans la section précédente.

0 votes

Très bonne discussion sur ce sujet, je peux suivre votre raisonnement et je suis d'accord, que la norme c est dans son état actuel pas assez clair concernant cette question.

0 votes

Je pense qu'il y a déjà une autre question en parlant de violation d'accès. Mais quoi qu'il en soit, bon travail sur ce point.

0 votes

J'aimerais également souligner que l'arithmétique des pointeurs n'est pas une erreur, tant que vous ne déréférencez pas ensuite le pointeur mal formé.

3voto

Peter Cordes Points 1375

Tout cela à cause de -D_FORTIFY_SOURCE=2 s'écrasant intentionnellement sur ce qu'il décide être dangereux.

Certains distros construisent gcc avec -D_FORTIFY_SOURCE=2 activé par défaut. Certains ne le font pas. Ceci explique toutes les différences entre les différents compilateurs. Il est probable que ceux qui ne plantent pas normalement le feront si vous compilez votre code avec -O3 -D_FORTIFY_SOURCE=2 .

Pourquoi n'échoue-t-il que si l'optimisation est activée ?

_FORTIFY_SOURCE nécessite une compilation avec optimisation ( -O ) pour garder la trace de la taille des objets à travers les casts/assignations de pointeurs. Voir les diapositives de cette conférence pour en savoir plus sur _FORTIFY_SOURCE .

Pourquoi strcpy() échoue-t-il ? Comment le faire ?

gcc appelle __memcpy_chk para strcpy seulement avec -D_FORTIFY_SOURCE=2 . Il passe 8 comme la taille de l'objet cible, car c'est ce qu'il pense que vous voulez dire. / ce qu'il peut comprendre à partir du code source que vous lui avez donné. Même chose pour strncpy en appelant __strncpy_chk .

__memcpy_chk s'interrompt volontairement. _FORTIFY_SOURCE peut aller au-delà des choses qui sont UB en C et interdire des choses qui semblent potentiellement dangereux . Cela lui donne le droit de décider que votre code n'est pas sûr. (Comme d'autres l'ont souligné, un membre de tableau flexible comme dernier membre de votre struct, et/ou une union avec un membre de tableau flexible, est la façon dont vous devriez exprimer ce que vous faites en C).


gcc prévient même que la vérification échouera toujours :

In function 'strcpy',
    inlined from 'main' at <source>:18:9:
/usr/include/x86_64-linux-gnu/bits/string3.h:110:10: warning: call to __builtin___memcpy_chk will always overflow destination buffer
   return __builtin___strcpy_chk (__dest, __src, __bos (__dest));
          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(de [gcc7.2 -O3 -Wall sur l'explorateur de compilateur Godbolt](https://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,source:'%23include+%3Cstdio.h%3E%0A%23include+%3Cstdlib.h%3E%0A%23include+%3Cstring.h%3E%0A%23include+%3Cstdint.h%3E%0A%23include+%3Cstddef.h%3E+//+For+offsetof()%0A%0Atypedef+struct+_pack%7B%0A++++//+The+type+of+%60c%60+doesn!'t+matter+as+long+as+it!'s+inside+of+a+struct.%0A++++int64_t+c%3B%0A%7D+pack%3B%0A%0Aint+main()%7B%0A++++pack+*p%3B%0A++++char+str%5B9%5D+%3D+%22aaaaaaaa%22%3B+//+Input%0A++++size_t+len+%3D+offsetof(pack,+c)+%2B+(strlen(str)+%2B+1)%3B%0A++++p+%3D+malloc(len)%3B%0A++++//+Version+1:+crash%0A++++++++strcpy((char*)%26(p-%3Ec),+str)%3B%0A++++//+Version+2:+crash%0A++++++++strncpy((char*)%26(p-%3Ec),+str,+strlen(str)%2B1)%3B%0A++++//+Version+3:+works!!%0A++++++++memcpy((char*)%26(p-%3Ec),+str,+strlen(str)%2B1)%3B%0A++++puts((char*)%26(p-%3Ec))%3B%0A++++free(p)%3B%0A++return+0%3B%0A%7D%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:35.430172879524584,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((g:!((h:compiler,i:(compiler:g63,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),libs:!(),options:'-xc+-Wall+-O3++-D_FORTIFY_SOURCE%3D2',source:1),l:'5',n:'0',o:'x86-64+gcc+6.3+(Editor+%231,+Compiler+%231)',t:'0')),k:29.6056185845489,l:'4',m:77.21261444557477,n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compiler:1,editor:1),l:'5',n:'0',o:'%231+with+x86-64+gcc+6.3',t:'0')),l:'4',m:22.78738555442523,n:'0',o:'',s:0,t:'0')),k:32.528234250641894,l:'3',n:'0',o:'',t:'0'),(g:!((g:!((h:compiler,i:(compiler:clang500,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),libs:!(),options:'-xc+-std%3Dgnu11+-Wall+-Wextra+-Wpedantic++-O3+++-D_FORTIFY_SOURCE%3D2',source:1),l:'5',n:'0',o:'x86-64+clang+5.0.0+(Editor+%231,+Compiler+%233)',t:'0')),k:32.04159286983353,l:'4',m:76.2970498474059,n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compiler:3,editor:1),l:'5',n:'0',o:'%233+with+x86-64+clang+5.0.0',t:'0')),header:(),l:'4',m:23.702950152594106,n:'0',o:'',s:0,t:'0')),k:32.04159286983353,l:'3',n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4) ).


Pourquoi est-ce que memcpy() échouent indépendamment de -O niveau ?

JE NE SAIS PAS.

gcc l'inline complètement, juste un 8B load/store + un 1B load/store. (Cela semble être une optimisation manquée ; il devrait savoir que malloc ne l'a pas modifié sur la pile, donc il pourrait simplement le stocker à nouveau à partir d'immediates au lieu de le recharger. (Ou mieux, garder la valeur 8B dans un registre).

0 votes

Puis-je définir ce genre de choses en code ? Par exemple. #define _FORTIFY_SOURCE 0 pour le désactiver de force sans tenir compte des arguments de la ligne de commande du compilateur ?

0 votes

@iBug : Je viens de l'essayer sur Godbolt ( godbolt.org/g/6aegks ). #undef _FORTIFY_SOURCE avant tout #include <> modifie la sortie de l'asm pour ne pas appeler de _chk donc oui, vous pouvez la désactiver de force dans le code source même si elle est explicitement activée sur la ligne de commande.

2voto

Jean-François Fabre Points 94672

Pourquoi compliquer les choses ? Surcomplexifier comme vous le faites donne juste plus d'espace pour comportement indéfini dans cette partie :

memcpy((char*)&p->c, str, strlen(str)+1);
puts((char*)&p->c);

avertissement : passage de l'argument 1 de 'puts' par un type de pointeur incompatible pe [-Wincompatible-pointer-types] puts(&p->c) ;

vous finissez clairement dans une zone de mémoire non allouée ou dans un endroit accessible en écriture si vous avez de la chance...

L'optimisation ou non peut modifier les valeurs des adresses, et cela peut fonctionner (puisque les adresses correspondent), ou pas. Il suffit de ne peut pas faire ce que vous voulez faire (en gros mentir au compilateur )

Je le ferais :

  • allouer juste ce qui est nécessaire pour la structure, ne pas prendre en compte la longueur de la chaîne à l'intérieur, c'est inutile.
  • n'utilisez pas gets car elle est dangereuse et obsolète.
  • utiliser strdup au lieu de l'outil memcpy que vous utilisez puisque vous manipulez des chaînes de caractères. strdup n'oubliera pas d'allouer le nul-terminateur, et le placera dans la cible pour vous.
  • n'oubliez pas de libérer la chaîne dupliquée
  • lisez les avertissements, put(&p->c) est un comportement indéfini

test.c:19:10 : avertissement : passage de l'argument 1 de 'puts' depuis un type de pointeur incompatible pe [-Wincompatible-pointer-types] puts(&p->c) ;

Ma proposition

int main(){
    pack *p = malloc(sizeof(pack));
    char str[1024];
    fgets(str,sizeof(str),stdin);
    p->c = strdup(str);
    puts(p->c);
    free(p->c);
    free(p);
  return 0;
}

0 votes

La remarque sur strdup() est bon, une remarque sur getline() serait encore mieux...

0 votes

Ok, alors qu'est-ce qui justifie les downvotes ? getline ? a oublié de libérer la chaîne dupliquée.

0 votes

Je n'ai pas voté à la baisse. J'ai trouvé que votre réponse était bonne. Je me demande qui a rétrogradé, et pour quelle raison...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X