【问题标题】:Unknown C String Truncation/Overwrite未知的 C 字符串截断/覆盖
【发布时间】:2016-10-16 18:16:38
【问题描述】:

我在简单的字符串操作中遇到了一个有趣的内存问题。问题本身实际上并不在于字符串的读取,而是在我尝试调用字符串之前。

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

每当我运行它时,如果在 char *newToken 之后被截断的字符串是 malloc 的。所以这个结果的打印输出是

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

有人知道这是什么吗?我查看了 malloc 的其他示例,但无法弄清楚这里出了什么问题。

编辑:完整代码如下。请注意,我是刚开始 C 的大学生,所以无论如何它并不完美。但它一直有效,直到出现此错误。

函数调用如下。 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 reading\n", argv[2]);
        exit(1);
    }

    if (commandList == NULL){
        fprintf(stderr,"file %s could not be opened for reading\n", 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 understood\n",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 understood\n",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]== ' ' && 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]) && 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 && *argv[argIndex] == '-')
        {
        /* check if stdin, represented by "-" is an argument */
        /* if so, the end of options has been reached */
        if (argv[argIndex][1] == '\0') return argIndex;

        separateArg = 0;
        argUsed = 0;

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

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

        if (separateArg && argUsed)
            ++argIndex;

        ++argIndex;
        }

    return argIndex;
}


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

}

读取字符串()

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 failed\n");
        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 failed\n");
            fprintf(stderr,"no closing double quote\n");
            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 failed\n");
                fprintf(stderr,"escaped character missing\n");
                exit(6);
                }
            buffer[index] = convertEscapedChar(ch);
            }
        else
            buffer[index] = ch;
        ++index;
        ch = fgetc(fp);
        }

    buffer[index] = '\0';

    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

谢谢!

【问题讨论】:

  • 调用代码在哪里?
  • 你知道sizeof(100) + 1int 大一,对吧?大概5个左右……
  • 我不知道。谢谢,但遗憾的是这并没有解决错误。
  • 为什么要分配比指向字符的指针多一个字节,将该值存储在command 中,然后通过将其他内容分配给command 将其丢弃?坦率地说,很多这样的代码毫无意义,它以奇怪的方式失败也就不足为奇了。
  • 那是为了解决这个问题。我和其他许多人一样将其恢复为变量赋值。我同意这没有任何意义,但我想看看预先分配它是否有帮助。

标签: c memory truncation


【解决方案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);

因为tokenchar*,所以*tokenchar。所以sizeof(*token)sizeof(char)——绝对不是你想要的。

【讨论】:

  • 谢谢,这很有意义。我刚刚对此进行了试验,切换了我的 removeInvalid 和 removeSpaces 函数调用。然后错误在 removeinvalid 中消失,并在 removeSpaces 中重新出现。完成 char *output 声明以使其成为令牌大小的最佳方法是什么?删除指针会起作用吗?
  • 您需要为输入字符串中的每个字符加上一个字节作为终止零字节。因此strlen(token)+1.
  • 但是,如果您包含您不理解的内容,请不要期望代码完全可以工作。如果您试图理解代码,请先删除任何此类内容。它没有生产力,实际上对理解有害——即使它有效,你甚至不明白你改变了什么。如果它是偶然的(因为它把问题转移到别处),你会主动添加误解。
  • 哦,对不起。我没有看到您实际上将答案放在您的帖子中。我的眼睛怎么扫过它。我已经做出了改变,我的程序确实有效。但是,我很好地听取了您的警告。我明白,如果我不理解,那么我将永远不会学习,因此永远不会变得更好,在我的代码中会出现级联问题。我要写一个回答我的一个问题。让我知道我的回答是否准确。
【解决方案2】:

您几乎可以肯定在您没有向我们展示的代码的某些部分中存在缓冲区溢出。如果我猜的话,我会说您为 token 分配的存储空间太少,无法包含您首先写入其中的完整字符串。

您是否曾使用与removeInvalid() 中相同的错误代码分配token

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

【讨论】:

  • 谢谢。所以它在代码的另一部分。你知道有什么技巧可以快速找到这样的内存溢出吗?还是我只需要检查每个 malloc 调用?我现在就开始研究令牌调用。
  • 您可以使用 valgrind (valgrind.org),但我怀疑问题在于您在代码的其他部分使用了类似于 sizeof(100) 的东西。这分配的字节数远少于 100 个。
  • 我已经这样做了。再次扫描我的代码。如果你想看的话,我已经添加了 readString。我怀疑它有任何错误的代码,因为它是我的教授创建的模块。
  • 上面的评论应该在大卫的帖子下面。
【解决方案3】:
char *readHelper(FILE *in){
    char * token = malloc(sizeof(char *) + 1);
    if (stringPending(in)){
        token = readString(in);
    }
    else {
        token = readToken(in);
    }
    return token;
}

如果不能看到readStringreadToken,就很难理解这一点,但这不可能是正确的。

首先,您分配的字节比指向一个或多个字符的指针所需的多一个字节。这样的东西有什么用?如果您不存储指向一个或多个字符的指针,为什么要使用sizeof(char *)?如果要存储一个指向一个或多个字符的指针,为什么要添加一个?很难想象导致那行代码的原因。

然后,在if 中,您会立即丢失从malloc 返回的值,因为您使用它来存储其他内容来覆盖token。如果您不打算使用分配给 token 的值,那您为什么要分配它?

坦率地说,很多这样的代码根本没有任何意义。没有 cmets,很难理解其中的原因,因此我们可以指出它的问题所在。

要么那行代码背后有推理,在这种情况下,这是完全错误的推理。或者更糟糕的是,这行代码是在没有任何理由的情况下添加的,希望它能以某种方式工作。两种方法都不会产生工作代码。

当您尝试调试代码时,请先删除您通过实验添加的任何内容或您不理解的内容。如果您确实理解malloc(sizeof(char *) + 1),那么请说明您认为它的作用,以便您的理解得到纠正。

为什么你认为你需要一个比指向一个或多个字符的指针大一字节的缓冲区?

【讨论】:

  • 该行代码的原因是试图将其分配给我声明变量的 char* 大小。然后在末尾为空指针添加 1。我现在看到这是错误的并已将其删除。我在一个实验中做了它来修复代码。我编辑了代码以删除此类错误代码并将其读入 OP。
  • @KurtAnderson 同样,绝对至关重要删除任何实验性或您不理解的代码。否则,将无法进行调试。
  • 另外两件事:1) C 风格的字符串以单个零字节结束,而不是空指针(通常超过一个字节)。 2)那个零字节将终止字符并在内存中跟随它们,而不是任何指针。
  • 您在cleanUpString 中仍然遇到同样的问题。看output
【解决方案4】:

在 David Schwartz 和其他发帖人的帮助下,我找到了问题中的错误。当我为我的令牌/输出分配内存时,我没有分配足够的空间。使用

的错误代码
malloc(sizeof(100) + 1);

malloc(sizeof(*token) + 1);

两者都只产生了几个要分配的字节。这导致了缓冲区问题,导致发生随机字母和数字/截断。第一个导致空间相当于 int + 1 和第二个在 char + 1 中。(因为我正在使用 sizeof token,这正是它最初开始时的大小,一个 char

为了解决这个问题,我将令牌变量的分配更改为

malloc(strlen(token) + 1);

这分配了一个相当于令牌的“字符串”长度 + 1 的空间。为我的问题留出适当的空间,最终得到

【讨论】:

  • 顺便说一句,你还有很多个问题。您刚刚修复了导致代码崩溃的问题。一个主要问题是你从不考虑你分配的对象的生命周期,因此当你用完它们时没有能力释放它们。
  • 是的,我知道。就像我在原帖中所说的那样,这是一项大学作业,仅用于教授 AVL 树的使用。我们被明确告知此时不要担心内存/对象管理或释放,因为这将在后面的课程中介绍。感谢您的所有帮助。
  • 这很不幸。他们本质上是在强迫你学习坏习惯。人们经常会回到他们最初学到的东西上,而你只有一次机会第一次学到东西。这是教人 C 的一种糟糕的方式。他们强迫你使用你不理解的概念并告诉你不理解它们。你应该怎么学那样的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-01
  • 2017-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-04
相关资源
最近更新 更多