[C] segfault incomprise

Je fais quelques exercices en C, et je rencontre une erreur que je ne comprend pas (c’est là qu’on voit que le python n’aide pas).
J’ai une fonction :

char * getmpdstat() {

Elle est donc censée retourner une chaine de caractères.
Lorsque par contre, je veux qu’elle retourne une chaîne vide, il y a erreur de segmentation.

Erreur avec : return "";

Fonctionne avec : return smprintf("")

Sachant que la fonction smprintf est la suivante :

[code]char *
smprintf(char *fmt, …)
{
va_list fmtargs;
char *buf = NULL;

va_start(fmtargs, fmt);
if (vasprintf(&buf, fmt, fmtargs) == -1){
	fprintf(stderr, "malloc vasprintf\n");
	exit(1);
}
va_end(fmtargs);

return buf;

}
[/code]

Quelqu’un peut m’éclairer?

EDIT
Attention, ma réponse est fausse. Passez votre chemin. Vous êtes prévenus.

En lisant bien le code de smprintf, de bas en haut (si si) :

Et buf est défini comme ça :

Autrement dit, buf est un pointeur comme dans tous les bidule *truc en C, truc est un pointeur, un nombre qui représente le lieu pour trouver le contenu *truc de type bidule.
Quand tu écris :

Tu demandes à return de renvoyer un contenu et non pas un lieu. D’où l’erreur.

[quote=“jcsm33”]Quand tu écris :

Tu demandes à return de renvoyer un contenu et non pas un lieu. D’où l’erreur.[/quote]
Autant je sais pas pourquoi son code plante autant ta réponse est fausse. Les chaines entre " sont des pointeurs sur char, donc ça reste des pointeurs, d’ailleurs mon exemple montre bien qu’il n’y a pas de cast implicite de fait :

[code]> % less coucou.c
#include “stdio.h”
#include “stdlib.h”

char* foo() {
return “Hello world !!!”;
}

int main(void) {
printf("%s\n", foo());
return EXIT_SUCCESS;
}

% CFLAGS="-Wall -pedantic -Wextra -Wreturn-type -Wconversion" make -B coucou
cc -Wall -pedantic -Wextra -Wreturn-type -Wconversion coucou.c -o coucou
% ./coucou
Hello world !!![/code]

On peut plus se poser la question de savoir vers quoi point ce pointeur et si ce n’est pas vers le tas qui serait alors nettoyé à la sortie de la fonction, mais si c’était le cas mon exemple ne marcherais pas.

C’est tout à fait juste. Pardon pour cette énormité. Peux-tu tester ton exemple avec la chaîne vide “” ?

Je me disais aussi…
Avec une chaîne vide, c’est que j’ai tenté en premier, mais ce n’est pas mieux.
Du coup, pour réinitialiser une chaîne, je fais par défaut :

Attend tu parle d’autre chose là d’un coup. Tu nous explique que tu veut renvoyer une chaine dans ton premier message et là tu parle de réinitialisée (avec une méthode assez moyenne).

Tu fais quoi avec ta chaine ?

Pardon je m’exprime mal.
J’ai donc essayé en retournant une chaîne vide, et ça ne fonctionnait pas.

Du coup, à la place de faire un

Je faisais

chaine[0] = '\0'; return chaine;

edit : cette deuxième méthode ne fonctionne en fait pas. Je n’arrive pas à démêler ce comportement.

edit2 : Le code complet est là, dans la fonction getmpdstat() : yeuxdelibad.net/hg/coolrepo/file … wmstatus.c

Je test le code afin d’en savoir un peu plus;
En attendant, pourquoi retourner une chaine vide ? Pourquoi ne pas return NULL ?

EDIT: en fait, les archives téléchargés sont corrompus … tu peux donner la ligne pour faire un git clone ?

Un clone te ferait cloner TOUT. ça ne t’aiderait pas.
Tu as tout à télécharger à la main ici : yeuxdelibad.net/coolrepo/dwmstatus/

Ok, m’enfin j’peux pas tester et analyser l’exec du programme, donc difficile de trouver précisement le problème;

Retourne un NULL;

Ben si, je t’ai donné un lien au dessus. Le programme je l’ai modifié pour qu’il marche tant bien que mal, mais l’erreur de segmentation je ne la pige pas.

Tiens, en archive : yeuxdelibad.net/~public/dwmstatus.tar.gz

retourner NULL fonctionne oui, dans le sens où ça ne crashe pas. Mais à l’affichage, il met “(null)”, alors que je voudrais une chaîne vide.

...
dwmstatus.c:15:24: fatal error: mpd/client.h: Aucun fichier ou dossier de ce type
16% [alex:/tmp/test/dwmstatus]./dwmstatus 
./dwmstatus: error while loading shared libraries: libmpdclient.so.2: cannot open shared object file: No such file or directory

[quote=“haleth”] ... dwmstatus.c:15:24: fatal error: mpd/client.h: Aucun fichier ou dossier de ce type 16% [alex:/tmp/test/dwmstatus]./dwmstatus ./dwmstatus: error while loading shared libraries: libmpdclient.so.2: cannot open shared object file: No such file or directory [/quote]
Eh oui, il faut les librairies pour mpd : libmpdclient-dev :wink:

Bouahp, j’ai honte, pas vu que c’était une lib hors projet …

Des trucs à redire:

  • ligne 45:
    ln[strlen(ln)-1]=’\0’;
    Tu cherches bien à supprimer le dernier caractère, et pas à insérer un \0 en bout de chaîne, oui ?

  • Les smprintf, tu dois free la valeur du retour (y’a plein de memleak en l’état)

Pour ton problème, y’a deux trucs à savoir;
Avec smprintf, tu renvoies un pointeur, donc le free(mpd) ligne 387 est OK;
Avec return("");, tu renvoies NULL, et le free(NULL) segfault;

Illustration (lignes à placer entre 386 et 387 par exemple):

[..]
        printf("DEBUG: %i\n", mpd);
        char* toto = "";
        printf("DEBUG: %i\n", *toto);
[..]

1% [alex:/tmp/test/dwmstatus]./dwmstatus 
fifo creation: File exists
mpd connection: Operation now in progress
DEBUG: 4204607
DEBUG: 0

Du coup, j’me demande pourquoi avec NULL il affiche (null), et avec “” il n’affiche rien. Ce devrait être identique, non ?

Non, du moins pas en C ni en C++…

Crée un pointeur vers l’adresse 0.

Crée un pointeur vers un caractère dont la valeur est 0 (’\0’).

Comme je le disais plus haut, je pense que c’est un problème de pile ou de tas.
Avec la syntaxe de mon second exemple, la chaine est sur la pile. Je ne sais pas sur quelle instruction le segfault apparaît mais sur la pile, tu ne peux pas libérer manuellement de la mémoire (donc free(ptr); avec ptr initialisé comme dans le secnd exemple va planter).

Cela sert à remplacer le retour à la ligne \n par le caractère de fin de chaîne.

Je croyais ne pas avoir oublié de free. À chaque fois je vais un return(smprintf("%s",chaine), et je libère à chaque cycle le contenu de la variable dans laquelle a été fait le retour. J’ai encore mal compris un truc…

bon, j’ai donc compris que faire un free(""), forcément, ça plante.
faire un free(NULL), ça sert juste à rien, du coup ça ne plante pas.

Et le “(null)”, s’il apparaît c’est parceque la fonction smprintf, qui appelle vasprintf, convertit un NULL en une chaîne “(null)”.

C’est un peu capilotracté, mais je crois avoir compris.

Merci!

ps : Ah, et j’ai trouvé les free manquants!

Quand tu retournes une chaîne vide “”, tu ne retournes pas un char* alloué dynamiquement mais un const char* alloué statiquement. Deux différences :

  • tu ne peux pas modifier le contenu de la chaîne (const)
  • cette chaîne a été allouée statiquement lors de la compilation, donc il n’y a pas de mémoire à libérer car elle fait partie du binaire (section de données constantes, probablement protégées contre l’écriture si l’OS le permet). Si tu essayes malgré tout de la libérer, on tombe dans du Undefined Behaviour (UB) et là tout peut arriver (ça peut donner l’apparence de marcher, ça peut te donner une bête erreur de runtime, ça peut formater ton disque dur ou encore faire exploser la Lune, suivant ce que ton compilateur a choisi de faire)

Si ton API est censée te renvoyer une chaîne alloué dynamiquement (ie. en tant qu’utilisateur de l’API tu libères la chaîne plus tard avec free) alors tu ne dois pas renvoyer une chaîne statique car le free provoque l’UB.

La solution ? Allouer une chaîne dynamiquement et retourner ce pointeur.

char* chaine_vide = malloc(1); chaine_vide[0] = 0; // null terminator pour indiquer une chaîne vide return chaine_vide;

Et là t’es tranquille : tu peux free ta chaîne.

Selon la sémantique exacte de ton API, tu peux peut-être renvoyer un pointeur nul à la place (return 0;). Mais faut se méfier : des fois un pointeur nul et une chaîne vide veulent dire différentes choses donc c’est pas une solution générique…

Merci pour ces explications ! :slightly_smiling:

Dites donc, on a eu chaud pour la lune :stuck_out_tongue: