1
votes

C printf changer le contenu de la variable à l'intérieur de la fonction

Donc j'écris une petite fonction pour analyser les chemins, ça ressemble à ceci:

buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
buffer(buffer(%s)
)
buffer(hello)
hello

Maintenant, il y a un comportement indéfini dans mon code, ça ressemble au printf code> dans la méthode principale change la variable buffer ... comme vous pouvez le voir, la sortie de ce programme est:

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

int parse_path() {
    char *pathname = "this/is/a/path/hello";
    int char_index = 0;
    char current_char = pathname[char_index];
    char *buffer = malloc(2 * sizeof(char));
    char *current_char_str = malloc(2 * sizeof(char));

    while (current_char != '\0' && (int)current_char != 11) {
        if (char_index == 0 && current_char == '/') {
            char_index++; current_char = pathname[char_index];
            continue;
        }

        while (current_char != '/' && current_char != '\0') {
            current_char_str[0] = current_char;
            current_char_str[1] = '\0';

            buffer = (char *)realloc(buffer, (strlen(buffer) + 2) * sizeof(char));
            strcat(buffer, current_char_str);

            char_index++; current_char = pathname[char_index];
        }

        if (strlen(buffer)) {
            printf("buffer(%s)\n", buffer);
            current_char_str[0] = '\0';
            buffer[0] = '\0';
        }

        char_index++; current_char = pathname[char_index];
    }
};

int main(int argc, char *argv[]) {
    parse_path();
    printf("hello\n");
    return 0;
}

I ont regardé d'autres articles où le même genre de problème est mentionné et les gens m'ont dit d'utiliser un tableau de caractères static etc. mais cela ne semble pas aider. Des suggestions?

Pour une raison quelconque, à un moment donné dans ce programme, la chaîne "hello" de printf est présente dans mon buffer variable.

c gcc

10 commentaires

strcat (buffer, current_char_str); mais le buffer n'a jamais été initialisé.


realloc peut renvoyer null, en particulier lorsque strlen (buffer) du tampon non initialisé est trop grand.


@PaulOgilvie, en effet je m'interrogeais sur la même chose strcat (buffer, current_char_str); et en lisant ceci


Vous faites strlen (buffer) dans le premier realloc tandis que le buffer pointe vers la mémoire non initialisée


buffer = (char *) realloc (buffer, (strlen (buffer) + 2) * sizeof (char)); ne doit pas inclure (char *) (pas besoin pour lancer le retour de malloc, calloc, realloc ) et * sizeof (char) (toujours 1 ). Vous ne devez pas réallouer le pointeur lui-même, mais plutôt utiliser un pointeur temporaire et valider que realloc a réussi avant d'attribuer un nouveau bloc à buffer , par exemple void * tmp = realloc (buffer, (strlen (buffer) + 2); if (! tmp) {perror ("realloc-tmp"); break; buffer = tmp; . Puis si realloc échoue, vous n'écrasez pas le buffer par NULL créant une fuite de mémoire.


@ DavidC.Rankin J'ai fait ce que vous avez dit et maintenant j'obtiens la chaîne d'erreur "realloc-tmp" dans ma variable de tampon ... hahah


) et } manquants dans le commentaire de @ DavidC.Rankin. void * tmp = realloc (buffer, (strlen (buffer) + 2)); if (! tmp) {perror ("realloc-tmp"); break;} buffer = tmp;


Doit être void * tmp = realloc (buffer, strlen (buffer) + 2); (trop de '(' )


@cdarke oui j'ai figuré, mais toujours le même résultat: la variable / buffer est toujours affectée par le printf et la chaîne perror ...


Cela est uniquement dû au fait que buffer n'est pas initialisé après avoir été alloué. Soit en faire une chaîne vide (par exemple * buffer = 0; après malloc ), soit utilisez calloc à la place pour définir la nouvelle mémoire à zéro .


3 Réponses :


2
votes

Vous strcat quelque chose à buffer mais le buffer n'a jamais été initialisé. strcat recherchera d'abord le premier caractère nul, puis copiera la chaîne pour y concaténer. Vous écrasez probablement de la mémoire qui n'est pas la vôtre.

Avant la boucle externe while , faites:

    *buffer= '\0';


1 commentaires

Toujours le même résultat même si je fais ça



4
votes

Sebastian, si vous rencontrez toujours des problèmes après la réponse @PaulOgilvie , il est fort probable que ce ne soit pas comprendre sa réponse. Votre problème est dû au fait que le buffer est alloué mais non initialisé . Lorsque vous appelez malloc , il alloue un bloc d'au moins la taille demandée, et renvoie un pointeur vers l'adresse de début du nouveau bloc - mais ne fait rien avec le contenu du nouveau bloc - ce qui signifie que le bloc est composé de valeurs aléatoires complètes qui se trouvent juste dans la plage d'adresses du nouveau bloc.

Donc, lorsque vous appelez strcat (buffer, current_char_str); le la première fois et il n'y a rien d'autre que des déchets aléatoires dans buffer et pas de caractère nul-terminating - vous appelez Undefined Behavior . (il n'y a pas de fin de chaîne dans buffer à trouver)

Pour corriger l'erreur, il vous suffit de créer un buffer an chaîne-vide après avoir été allouée en définissant le premier caractère sur le caractère nul-terminating , ou utilisez plutôt calloc pour allouer le bloc qui garantira tout les octets sont mis à zéro.

Par exemple:

> parsepath2.exe another/path/that/ends/in/a/filename
buffer (another)
buffer (path)
buffer (that)
buffer (ends)
buffer (in)
buffer (a)
buffer (filename)
hello

De même, ne codez pas en dur les chemins ou les nombres (qui inclut le 0 et 2 , mais nous les laisserons glisser pour l'instant). Le codage en dur "this / is / a / path / hello" dans parse_path () make est une fonction plutôt inutile. Au lieu de cela, faites de votre variable chemin votre paramètre afin que je puisse prendre n'importe quel chemin que vous voulez lui envoyer ...

Alors que toute l'idée de réallouer «Avoir 2 caractères à la fois est plutôt inefficace, vous devez toujours réallouer avec un pointeur temporaire plutôt que le pointeur lui-même. Pourquoi? realloc peut échouer et échoue et quand il le fait, il renvoie NULL . Si vous utilisez le pointeur lui-même, vous écraserez votre adresse de pointeur actuelle par NULL en cas d'échec, perdant l'adresse de votre bloc de mémoire existant pour toujours créer une fuite de mémoire. Au lieu de cela,

> parsepath2.exe
buffer (this)
buffer (is)
buffer (a)
buffer (path)
buffer (hello)
hello

Un court exemple incorporant les correctifs et le pointeur temporaire serait:

#include <stdio.h>
#include <limits.h>  /* for PATH_MAX - but VS doesn't provide it, so we check */

#ifndef PATH_MAX
#define PATH_MAX  2048
#endif

void parse_path (const char *pathname)
{
    const char *p = pathname;
    char buffer[PATH_MAX], *b = buffer;

    while (*p) {
        if (*p == '/') {
            if (p != pathname) {
                *b = 0;
                printf ("buffer (%s)\n", buffer);
                b = buffer;
            }
        }
        else
            *b++ = *p;
        p++;
    }
    if (b != buffer) {
        *b = 0;
        printf ("buffer (%s)\n", buffer);
    }
}

int main (int argc, char* argv[]) {

    char *path = argc > 1 ? argv[1] : "this/is/a/path/hello";
    parse_path (path);
    printf ("hello\n");

    return 0;
}

( remarque: votre logique est assez torturée ci-dessus et vous pouvez simplement utiliser un tampon fixe de taille PATH_MAX (y compris limits.h ) et vous dispenser d'allouer. Sinon, vous devriez allouer un certain nombre de caractères prévu pour le buffer pour commencer, comme strlen (chemin) qui assurerait un espace suffisant pour chaque composant de chemin sans réallouer. Je préfère surallouer par 1000 caractères que foutre l'indexation se soucier de la réattribution de 2 caractères à la fois ...)

Exemple d'utilisation / sortie

> bin\parsepath.exe
buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
hello


10 commentaires

Mérite plus que +1. :)


Content de vous aider. On pouvait dire qu'il y avait encore un peu de confusion qui devait être supprimée. Espérons que cela aidera.


C'est une réponse très complète!


Vous amenez le cheval à l'eau, mais pour une raison quelconque, il ne boirait pas. Alors je lui ai juste apporté de l'eau et je l'ai versée sur sa tête :) Je ne sais pas s'il a vraiment bu, mais il s'est vraiment mouillé ...


Merci beaucoup pour votre réponse descriptive ... le plus drôle est que, même si j'exécute votre code qui est censé être un "correctif", j'obtiens toujours le même problème où le printf écrit dans la variable tampon. Peut-il y avoir quelque chose qui ne va pas avec mon compilateur ou quelque chose? J'utilise gcc version 6.3.0 20170516 (Debian 6.3.0-18 + deb9u1)


@SebastianKarlsson - ce n'est pas possible sauf s'il y a un problème de jeu de caractères. Vous dites que vous utilisez gcc 6.3.0 , mais sur quoi? Est-ce dans WSL ou s'agit-il d'une installation MinGW. La seule chose à laquelle je peux penser est que vous avez capturé un retour chariot dans le texte que vous utilisez comme entrée, vous utilisez le jeu de caractères UTF-8 (ou UTF-16), ou quelque chose de cette manière. Copiez et collez mon code dans un éditeur et assurez-vous que vous enregistrez avec les fins de ligne '\ n' . Puis compilez gcc -Wall -Wextra -pedantic -std = c11 -Ofast -o parsepath parsepath.c (c'est 'Oh fast' pas zéro) et réessayez. (l'une ou l'autre version)


@ DavidC.Rankin toujours le même problème même si je compile avec vos options de compilation: / J'enregistre votre code dans ViM sur une machine Linux donc ça ne devrait pas être un problème


Quelle est votre configuration actuelle? Quel système d'exploitation, quel compilateur, etc.? J'ai compilé et exécuté à la fois sur Linux et Windows en utilisant gcc et VS et aucun problème (même en compilant avec -Wall -Wextra -pedantic sur gcc et avec / W3 sur VS) . C'est pourquoi je dis qu'il y a un problème de jeu de caractères quelque part. Êtes-vous sûr que les guillemets doubles que vous utilisez ne sont pas des guillemets non ASCII Web gauche / droite? Ce sera un problème subtil comme celui-là.


@ DavidC.Rankin J'utilise Debian 9 sur un Dell XPS 13 .... oui le fichier est enregistré au bon format! Maintenant, c'est bizarre, je l'ai fait fonctionner en ajoutant la modification de la condition dans la boucle while externe à: c! = '\ 0' && char_index . Donc, pour une raison quelconque, la variable buffer est remplie avec d'autres données ... même si ce n'est pas le cas puisque nous avons fait * buffer = 0; ... c'est vraiment étrange.


@SebastianKarlsson - J'ai trouvé le problème, vous aviez besoin d'un autre test if (current_char! = '\ 0') à la fin de votre boucle principale avant d'avancer à nouveau char_index ++; qui pourrait le faire avancer au-delà du * nul-terminator` s'il est déjà défini sur celui à la fin de la boucle précédente (une autre raison pour laquelle éviter plusieurs boucles imbriquées est une bonne idée)



1
votes

Il y a 2 problèmes principaux dans votre code:

  • les tableaux alloués par malloc () ne sont pas initialisés, vous avez donc un comportement indéfini lorsque vous appelez strlen (buffer) avant de définir un terminateur nul dans le tableau buffer pointe vers. Le programme pourrait simplement planter, mais dans votre cas, quel que soit le contenu présent dans le bloc de mémoire et après il est conservé jusqu'au premier octet nul.
  • juste avant la fin de la boucle externe, vous ne devez prendre le caractère suivant du chemin que si le caractère courant est un '/' . Dans votre cas, vous ignorez le terminateur nul et le programme a un comportement indéfini lorsque vous lisez au-delà de la fin de la constante de chaîne. En effet, l'analyse continue à travers une autre constante de chaîne "buffer (% s) \ n" et à travers encore une autre "hello" . Les constantes de chaîne semblent être adjacentes sans remplissage sur votre système, ce qui n'est qu'une coïncidence.

Voici une version corrigée:

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

void parse_path(const char *pathname) {
    int i, n;

    for (i = 0; pathname[i] != '\0'; i += n) {
        if (pathname[i] == '/') {
            n = 1;  /* skip path separators and empty items */
        } else {
            n = strcspn(pathname + i, "/");  /* get the length of the path item */
            printf("buffer(%.*s)\n", n, pathname + i);
        }
    }
}

int main(int argc, char *argv[]) {
    parse_path("this/is/a/path/hello");
    printf("hello\n");
    return 0;
}

Sortie:

buffer(this)
buffer(is)
buffer(a)
buffer(path)
buffer(hello)
hello

Notez cependant quelques problèmes restants :

  • l'échec de l'allocation n'est pas testé, ce qui entraîne un comportement non défini,
  • les blocs alloués ne sont pas libérés, ce qui entraîne des fuites de mémoire,
  • On ne sait pas pourquoi vous testez current_char! = 11 : vouliez-vous vous arrêter à TAB ou à une nouvelle ligne?

Voici une version beaucoup plus simple avec le même comportement:

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

void parse_path(const char *pathname) {
    int char_index = 0;
    char current_char = pathname[char_index];
    char *buffer = calloc(1, 1);
    char *current_char_str = calloc(1, 1);

    while (current_char != '\0' && current_char != 11) {
        if (char_index == 0 && current_char == '/') {
            char_index++; current_char = pathname[char_index];
            continue;
        }
        while (current_char != '/' && current_char != '\0') {
            current_char_str[0] = current_char;
            current_char_str[1] = '\0';

            buffer = (char *)realloc(buffer, strlen(buffer) + 2);
            strcat(buffer, current_char_str);

            char_index++; current_char = pathname[char_index];
        }
        if (strlen(buffer)) {
            printf("buffer(%s)\n", buffer);
            current_char_str[0] = '\0';
            buffer[0] = '\0';
        }
        if (current_char == '/') {
            char_index++; current_char = pathname[char_index];
        }
    }
}

int main(int argc, char *argv[]) {
    parse_path("this/is/a/path/hello");
    printf("hello\n");
    return 0;
}


0 commentaires