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 deuxstrcpy()
ymemcpy()
fonctionne sur Linux/WSL, et leputs()
ci-dessous donne ce que j'ai entré. - Avec
clang -O0
sur OSX le code se bloque avecstrcpy()
. - Avec
gcc/clang -O2
o-O3
sur Ubuntu/Fedora/WSL le code crashs ( !!) à l'adressestrcpy()
alors quememcpy()
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é enlong long c
(ouint64_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.
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) :
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.
0 votes
Puis-je vous demander si les membres du réseau flexible C99 sont une option pour vous ?
2 votes
Vous avez omis d'indiquer à quoi ressemblait le "crash". C'est toujours utile. Était-ce un
abort()
d'un code de vérification, ou s'agissait-il d'une violation d'accès (par exemple, SEH 0xC000.0005 sous Windows), etc. no un terme technique à ce niveau :-)0 votes
Il n'est pas clair (pour moi) si vous dites que le crash se produit pendant le strcpy/strncpy ou si c'est pendant les puts suivants.
0 votes
Question mise à jour. Un message d'erreur est inclus.
0 votes
Si vous ne pouvez pas utiliser la FMA, peut-être que ceci devrait fonctionner. rextester.com/PDP67976
0 votes
@Stargateur Vous pouvez voir mon projet réel et ce qui est fait dans
writeFile()
(Ligne 56-75). Je pense que le seul moyen est d'utiliser unvoid*
tampon.0 votes
@iBug what you link have the same problem that your exemple minimal reproductible Je vous conseille donc vivement de mettre à jour votre projet pour qu'il soit au moins conforme à la norme C99 afin d'utiliser le FMA ou d'utiliser la méthode que je vous donne. Note : stackoverflow.com/a/3225396/7076153