Неизвестное усечение / перезапись строки C

#c #memory #truncation

#c #память #усечение

Вопрос:

У меня интересная проблема с памятью при простой манипуляции со строками. Сама проблема на самом деле заключается не в чтении строки, а прямо перед ней, когда я пытаюсь вызвать строку.

 char *removeInvalid(char *token){
    fprintf(stderr," Before: %s n", token);
    char *newToken = malloc(sizeof(100)   1);
    fprintf(stderr," After: %s n", token);
}
 

Всякий раз, когда я запускаю это, строка, если она усекается сразу после символа *newToken , является malloc’d . Таким образом, распечатка этого приводит к

 Before: Willy Wanka's Chochlate Factory
After: Will Wanka's Chochlate F!
 

Кто-нибудь знает, что это такое? Я просмотрел другие примеры malloc, но не могу понять, что здесь не так.

РЕДАКТИРОВАТЬ: ПОЛНЫЙ КОД НИЖЕ. Обратите внимание, что я студент колледжа, который только начал изучать язык Си, так что он ни в коем случае не идеален. Но это работает до этой ошибки.

Вызовы функций выполняются следующим образом. Main-> initialReadAVL (эта часть работает отлично) Затем после вызова commandReadAVL, который идет commandReadAVL-> ReadHelper (здесь снова работает нормально. Затем CleanUpString-> removeSpaces(работает нормально) Затем CleanUpString-> removeInvalid(ВОТ ГДЕ ЭТО ОШИБКА)

 #include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "node.h"
#include "avl.h"
#include "scanner.h"
#include "bst.h"

/* Options */
int avlSwitch = 0;
int bstSwitch = 0;
int insertSwitch = 0;
int deleteSwitch = 0;
int frequencySwitch = 0;
int displaySwitch = 0;
int statisticSwitch = 0;

int ProcessOptions(int argc, char **argv);
char *cleanUpString(char *token);
char *turnToLowerCase(char *token);
char *removeSpaces(char *token);
char *removeInvalid(char *token);
char *readHelper(FILE *in);
void Fatal(char *fmt, ...);
void preOrder(struct node *root);
void initialReadAVL(avl *mainAVL, FILE *in);
void initialReadBST(bst *mainBST, FILE *in);
void commandReadBST(bst *mainBST, FILE *commandList);
void commandReadAVL(avl *mainAVL, FILE *commandList);

int main(int argc, char **argv) {
    struct avl *mainAVL;
    struct bst *mainBST;
    FILE *text;
    FILE *commandList;


    if(argc != 4){
        Fatal("There must be 4 arguments of form 'trees -b corpus commands' n");
    }

    int argIndex = ProcessOptions(argc,argv);

    text = fopen(argv[2], "r");
    commandList = fopen(argv[3], "r");

    //Protect against an empty file.
    if (text == NULL){
        fprintf(stderr,"file %s could not be opened for readingn", argv[2]);
        exit(1);
    }

    if (commandList == NULL){
        fprintf(stderr,"file %s could not be opened for readingn", argv[3]);
        exit(1);
    }


    if (avlSwitch){
        mainAVL = newAVL();
        initialReadAVL(mainAVL, text);
        preOrder(mainAVL->root);
        fprintf(stderr,"n");
        commandReadAVL(mainAVL, commandList);
        preOrder(mainAVL->root);
        fprintf(stderr,"n");
    }
    else if (bstSwitch){
        mainBST = newBST();
        initialReadBST(mainBST, text);
        preOrder(mainBST->root);
        commandReadBST(mainBST, commandList);
        preOrder(mainBST->root);
    }


    return 0;
}


void commandReadAVL(avl *mainAVL, FILE *commandList){
    char *command;
    char *textSnip;
    while(!feof(commandList)){
        command = readHelper(commandList);
        textSnip = readHelper(commandList);
        textSnip = cleanUpString(textSnip);

        if(command != NULL){
            switch (command[0]) {
            case 'i':
                fprintf(stderr,"%s n", textSnip);
                insertAVL(mainAVL, textSnip);
                break;
            case 'd':
                deleteAVL(mainAVL, textSnip);
                break;
            case 'f':
                break;
            case 's':
                break;
            case 'r':
                break;
            default:
                Fatal("option %s not understoodn",command);
            } 
        }

    }
}

void commandReadBST(bst *mainBST, FILE *commandList){
    char *command;
    char *textSnip;
    while(!feof(commandList)){
        command = readHelper(commandList);
        textSnip = readHelper(commandList);
        textSnip = cleanUpString(textSnip);
        if(command != NULL){
            switch (command[0]) {
                case 'i':
                    insertBST(mainBST, textSnip);
                    break;
                case 'd':
                    deleteBST(mainBST, textSnip);
                    break;
                case 'f':
                    break;
                case 's':
                    break;
                case 'r':
                    break;
                default:
                    Fatal("option %s not understoodn",command);
                } 
        }
    }
}


char *readHelper(FILE *in){
    char *token;
    if (stringPending(in)){
        token = readString(in);
    }
    else {
        token = readToken(in);
    }
    return token;
}

void initialReadBST(bst *mainBST, FILE *in){
    char *token;
    while(!feof(in)){

        token = readHelper(in);
        token = cleanUpString(token);
        if (token != NULL){
            insertBST(mainBST, token);
        }
    }
}

void initialReadAVL(avl *mainAVL, FILE *in){
    char *token;
    while(!feof(in)){

        token = readHelper(in);
        token = cleanUpString(token);
        if (token != NULL){
            insertAVL(mainAVL, token);
        }
    }
}

//Helper Function to clean up a string using all the prerequisites. 
char *cleanUpString(char *token){
    char *output = malloc(sizeof(*token)  1);
    if (token != NULL){
        output = removeSpaces(token);
         fprintf(stderr,"before : %s n", output);
        output = removeInvalid(output);
         fprintf(stderr,"%s n", output);
        output = turnToLowerCase(output);
        return output;
    }
    return NULL;

}

//Helper function to turn the given string into lower case letters
char *turnToLowerCase(char *token){
    char *output = malloc(sizeof(*token)   1);
    for (int x = 0; x < strlen(token); x  ){
            output[x] = tolower(token[x]);
        }
    return output;
}

//Helper function to remove redundent spaces in a string.
char *removeSpaces(char *token){
    char *output;
    int x = 0;
    int y = 0;

    while (x < strlen(token)){
        if (token[x]== ' ' amp;amp; x < strlen(token)){
            while(token[x] == ' '){
                x  ;
            }
            output[y] = ' ';
            y  ;
            output[y] = token[x];
            y  ;
            x  ;
        }
        else {
            output[y] = token[x];
            y  ;
            x  ;
        }

    }
    return output;

}

char *removeInvalid(char *token){
    fprintf(stderr," Before: %s n", token);
    char *newToken = malloc(sizeof(* token)  1);
    fprintf(stderr," After: %s n", token);


    int x = 0;
    int y = 0;
    while (x < strlen(token)){
        if (!isalpha(token[x]) amp;amp; token[x] != ' '){
            x  ;
        }
        else {
            newToken[y] = token[x];
            y  ;
            x  ;
        }
    }
    return newToken;
}


//Processes a system ending error. 
void Fatal(char *fmt, ...) {
    va_list ap;

    fprintf(stderr,"An error occured: ");
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);

    exit(-1);
    }


//Processes the options needed to be executed from the command line
int ProcessOptions(int argc, char **argv) {
    int argIndex;
    int argUsed;
    int separateArg;

    argIndex = 1;

    while (argIndex < argc amp;amp; *argv[argIndex] == '-')
        {
        /* check if stdin, represented by "-" is an argument */
        /* if so, the end of options has been reached */
        if (argv[argIndex][1] == '') return argIndex;

        separateArg = 0;
        argUsed = 0;

        if (argv[argIndex][2] == '')
            {
            separateArg = 1;
            }

        switch (argv[argIndex][1])
            {
            case 'b':
                bstSwitch = 1;
                break;
            case 'a':
                avlSwitch = 1;
                break;
            default:
                Fatal("option %s not understoodn",argv[argIndex]);
            }

        if (separateArg amp;amp; argUsed)
              argIndex;

          argIndex;
        }

    return argIndex;
}


void preOrder(struct node *root) {
    if(root != NULL)
    {
        fprintf(stderr,"%s ", root->key);
        preOrder(root->lChild);
        preOrder(root->rChild);
    }

}
 

ReadString()

 char *
readString(FILE *fp)
    {
    int ch,index;
    char *buffer;
    int size = 512;

    /* advance to the double quote */

    skipWhiteSpace(fp);
    if (feof(fp)) return 0;

    ch = fgetc(fp);
    if (ch == EOF) return 0;

    /* allocate the buffer */

    buffer = allocateMsg(size,"readString");

    if (ch != '"')
        {
        fprintf(stderr,"SCAN ERROR: attempt to read a string failedn");
        fprintf(stderr,"first character was <%c>n",ch);
        exit(4);
        }

    /* toss the double quote, skip to the next character */

    ch = fgetc(fp);

    /* initialize the buffer index */

    index = 0;

    /* collect characters until the closing double quote */

    while (ch != '"')
        {
        if (ch == EOF)
            {
            fprintf(stderr,"SCAN ERROR: attempt to read a string failedn");
            fprintf(stderr,"no closing double quoten");
            exit(6);
            }
        if (index > size - 2) 
            {
              size;
            buffer = reallocateMsg(buffer,size,"readString");
            }

        if (ch == '\')
            {
            ch = fgetc(fp);
            if (ch == EOF)
                {
                fprintf(stderr,"SCAN ERROR: attempt to read a string failedn");
                fprintf(stderr,"escaped character missingn");
                exit(6);
                }
            buffer[index] = convertEscapedChar(ch);
            }
        else
            buffer[index] = ch;
          index;
        ch = fgetc(fp);
        }

    buffer[index] = '';

    return buffer;
    }
 

ВВОД: Commands.txt

 i "Willy Wonka's Chochlate Factory"
 

ВХОДНЫЕ ДАННЫЕ testFile.txt

 a b c d e f g h i j k l m n o p q r s t u v w x y z
 

Спасибо!

Комментарии:

1. Где находится вызывающий код?

2. Ты же знаешь, что sizeof(100) 1 это на единицу больше, чем размер int , верно? Вероятно, 5 или около того…

3. Я этого не знал. Спасибо, однако, к сожалению, это не исправило ошибку.

4. Почему вы назначаете на один байт больше, чем необходимо для указателя на символ, сохраняете это значение в command , а затем выбрасываете его , назначая что-то еще command ? Грубо говоря, большая часть этого кода не имеет смысла, и совсем не удивительно, что он странным образом выходит из строя.

5. Это было при попытке исправить эту проблему. Я вернул это обратно, как и многие другие, просто к присвоению переменной. Я согласен, что это не имеет смысла, но я хотел посмотреть, помогло ли это preallocated вообще.

Ответ №1:

 char *turnToLowerCase(char *token){
    char *output = malloc(sizeof(*token)   1);
    for (int x = 0; x < strlen(token); x  ){
            output[x] = tolower(token[x]);
        }
    return output;
}
 

Вероятно, это ваша главная проблема. Вы выделяете достаточно места для двух символов, а затем продолжаете хранить намного больше. Вы, наверное, хотели:

     char *output = malloc(strlen(token)   1);
 

Поскольку token является a char* , *token является a char . Так sizeof(*token) sizeof(char) что — определенно не то, что вы хотите.

Комментарии:

1. Спасибо, это имеет большой смысл. Я просто экспериментировал с этим, переключая мои вызовы функций removeInvalid и removeSpaces . Затем ошибка исчезла в removeinvalid и снова появилась в removeSpaces. Каков был бы наилучший способ выполнить объявление вывода char *, чтобы он соответствовал размеру токена? Будет ли удаление указателя работать?

2. Вам нужен один байт для каждого символа во входной строке плюс один байт для завершающего нулевого байта. Следовательно strlen(token) 1 .

3. Но, пожалуйста, не ожидайте, что код вообще будет работать, если вы включаете вещи, которые вы не понимаете. Если вы пытаетесь разобраться в коде, сначала удалите все подобные вещи. Это непродуктивно и на самом деле вредно для понимания — даже если это работает, вы даже не понимаете, что вы изменили. И если это сработает случайно (потому что это переместит проблему в другое место), вы будете активно добавлять недоразумения.

4. О, прошу прощения. Я не видел, чтобы вы действительно помещали ответ в свой пост. Некоторые мои глаза взглянули на это. Я внес это изменение, и моя программа действительно работает. Однако я хорошо прислушиваюсь к вашему предупреждению. Я понимаю, что если я не пойму, то я никогда не научусь и, следовательно, никогда не стану лучше, каскадируя проблемы по всему моему коду. Я собираюсь написать ответ на свой единственный вопрос. Дайте мне знать, если я точен в своем ответе.

Ответ №2:

У вас почти наверняка есть переполнение буфера в какой-то части кода, которую вы нам не показываете. Если бы я мог догадаться, я бы сказал, что вы выделяете слишком мало места для хранения token полной строки, которую вы записываете в нее в первую очередь.

Вы случайно выделяли token , используя тот же ошибочный код, который у вас есть в removeInvalid() :

 malloc(sizeof(100)   1);
       ^^^^^^^^^^^ this doesn't allocate 101 characters, it allocates sizeof(int) 1
 

Комментарии:

1. Спасибо. Значит, это в другой части кода. Знаете ли вы какие-либо методы быстрого обнаружения такого переполнения памяти? Или мне просто нужно проверять каждый вызов malloc? Я начну изучать вызов токена прямо сейчас.

2. Вы могли бы использовать valgrind ( valgrind.org ), хотя я подозреваю, что проблема в том, что вы используете что-то подобное sizeof(100) в других частях вашего кода. Это выделяет намного меньше 100 байт.

3. С тех пор я это сделал. Повторное сканирование моего кода. Я добавил ReadString, если вы хотите посмотреть на это. Однако я сомневаюсь, что в нем есть какой-либо ошибочный код, поскольку это модуль, созданный моим профессором.

4. Комментарий выше должен был идти под сообщением Дэвида.

Ответ №3:

 char *readHelper(FILE *in){
    char * token = malloc(sizeof(char *)   1);
    if (stringPending(in)){
        token = readString(in);
    }
    else {
        token = readToken(in);
    }
    return token;
}
 

Трудно понять это, не имея возможности видеть readString или readToken , но это не может быть правильным.

Во-первых, вы выделяете на один байт больше, чем необходимо для указателя на один или несколько символов. Какая польза была бы от такой вещи? Если вы не сохраняете указатель на один или несколько символов, зачем использовать sizeof(char *) ? Если вы храните указатель на один или несколько символов, зачем его добавлять? Трудно представить себе рассуждения, которые привели к этой строке кода.

Затем, в if , вы сразу же теряете значение, которое получили обратно, malloc потому что вы перезаписываете token , используя его для хранения чего-то другого. Если вы не собирались использовать присвоенное вам значение token , зачем вы его вообще присвоили?

Грубо говоря, большая часть этого кода просто не имеет никакого смысла. Без комментариев трудно понять рассуждения, поэтому мы могли бы указать, что с этим не так.

Либо за этой строкой кода стояли какие-то рассуждения, и в этом случае это просто совершенно неправильные рассуждения. Или, что еще хуже, строка кода была добавлена без каких-либо рассуждений в надежде, что это как-то сработает. Ни один из методов не приведет к созданию рабочего кода.

Когда вы пытаетесь отлаживать код, сначала удалите все, что вы добавили экспериментально или что вы не поняли. Если вы понимаете malloc(sizeof(char *) 1) , пожалуйста, объясните, что, по вашему мнению, это делает, чтобы ваше понимание можно было исправить.

Почему вы решили, что вам нужен буфер, размер которого на один байт больше размера указателя на один или несколько символов?

Комментарии:

1. Аргументация этой строки кода заключалась в попытке присвоить ей размер char *, для которого я объявил переменную. Затем добавьте 1 для нулевого указателя в конце. Теперь я вижу, что это ошибочно, и удалил его. Я сделал это в ходе эксперимента, чтобы исправить код. Я отредактировал код, чтобы удалить такой ошибочный код, и перечитал его в OP.

2. @KurtAnderson Опять же, абсолютно необходимо , чтобы вы удалили любой код, который был экспериментальным или который вы не понимаете. Невыполнение этого требования делает отладку невозможной.

3. Две другие вещи: 1) Строки в стиле C завершаются одним нулевым байтом, а не нулевым указателем (который обычно составляет более одного байта). 2) Этот нулевой байт завершит символы и будет следовать за ними в памяти, а не за любым указателем.

4. И у вас все еще есть та же проблема cleanUpString . Посмотри на output это .

Ответ №4:

С помощью Дэвида Шварца и других постеров я смог найти ошибку в моей проблеме. Когда я выделял память для своего токена / вывода, я не выделял достаточно места.. Использование ошибочного кода

 malloc(sizeof(100)   1);
 

и

 malloc(sizeof(*token)   1);
 

оба из которых произвели выделение только пары байтов. Это вызвало проблему с буфером, вызывающую случайные буквы и цифры / усечение. Первое приводит к пробелу, эквивалентному int 1, а второе в char 1. (поскольку я брал токен sizeof, который является просто размером того, с чего он изначально начинался, символом)

Чтобы исправить это, я изменил распределение моей переменной токена на

 malloc(strlen(token)   1);
 

Это выделяет пространство, эквивалентное «строковой» длине токена 1. Предоставление подходящего пространства для моей проблемы, которое в конечном итоге приведет к пробелу <= token .

Комментарии:

1. Кстати, у вас гораздо больше проблем. Вы только что исправили ту, которая приводила к сбою вашего кода. Одна из основных проблем заключается в том, что вы никогда не учитываете время жизни выделяемых вами объектов и поэтому не можете освободить их, когда закончите с ними.

2. Да, я знаю. Как я уже говорил в исходном сообщении, это задание для колледжа, предназначенное только для обучения использованию дерева AVL. Нам специально сказано не беспокоиться об управлении памятью / объектами или освобождении на этом этапе, поскольку это будет в последующих уроках. Спасибо за всю вашу помощь.

3. Это прискорбно. По сути, они заставляют вас усваивать вредные привычки. Люди часто возвращаются к тому, что они узнали сначала, и у вас есть только один шанс узнать что-то в первый раз. Это ужасный способ научить людей C. Они заставляют вас использовать понятия, которые вы не понимаете, и говорят вам не понимать их. Как вы должны так учиться?