2

我正在做一本书的练习,将句子中的单词变成拉丁语。该代码在窗口 7 中运行良好,但是当我在 mac 中编译它时,出现了错误。

经过一些测试,错误来自那里。我不明白这个问题的原因。我为所有指针使用动态内存,并且还添加了空指针检查。

while (walker != NULL && *walker != NULL){
    free(**walker); 
    free(*walker);
    free(walker); 

    walker++;
}

完整源代码:

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

#define inputSize 81
void getSentence(char sentence [], int size);
int countWord(char sentence[]);
char ***parseSentence(char sentence[], int *count);
char *translate(char *world);
char *translateSentence(char ***words, int count);

int main(void){
    /* Local definition*/
    char sentence[inputSize];
    int wordsCnt;
    char ***head;
    char *result;

    getSentence(sentence, inputSize);
    head = parseSentence(sentence, &wordsCnt);

    result = translateSentence(head, wordsCnt);
    printf("\nFinish the translation: \n");
    printf("%s", result);


    return 0;
}

void getSentence(char sentence [81], int size){
    char *input = (char *)malloc(size);
    int length;

    printf("Input the sentence to big latin : ");
    fflush(stdout);
    fgets(input, size, stdin);

    // do not copy the return character at inedx of length - 1
    // add back delimater 
    length = strlen(input);
    strncpy(sentence, input, length-1);
    sentence[length-1]='\0';

    free(input);
}

int countWord(char sentence[]){
    int count=0;

    /*Copy string for counting */
    int length = strlen(sentence);
    char *temp = (char *)malloc(length+1);
    strcpy(temp, sentence);

    /* Counting */
    char *pToken = strtok(temp, " ");
    char *last = NULL;
    assert(pToken == temp);
    while (pToken){
        count++;

        pToken = strtok(NULL, " ");
    }

    free(temp);
    return count;
}
char ***parseSentence(char sentence[], int *count){
    // parse the sentence into string tokens
    // save string tokens as a array
    // and assign the first one element to the head
    char *pToken;
    char ***words;
    char *pW;

    int noWords = countWord(sentence);
    *count = noWords;

    /* Initiaze array */
    int i;
    words = (char ***)calloc(noWords+1, sizeof(char **));
    for (i = 0; i< noWords; i++){
        words[i] = (char **)malloc(sizeof(char *));
    }

    /* Parse string */
    // first element
    pToken = strtok(sentence, " ");

    if (pToken){
        pW = (char *)malloc(strlen(pToken)+1);
        strcpy(pW, pToken);
        **words = pW;
        /***words = pToken;*/

        // other elements
        for (i=1; i<noWords; i++){
            pToken = strtok(NULL, " ");
            pW = (char *)malloc(strlen(pToken)+1);
            strcpy(pW, pToken);
            **(words + i) = pW;
            /***(words + i) = pToken;*/
        }
    }

    /* Loop control */
    words[noWords] = NULL;


    return words;
}

/* Translate a world into big latin */
char *translate(char *word){
    int length = strlen(word);
    char *bigLatin = (char *)malloc(length+3);

    /* translate the word into pig latin */
    static char *vowel = "AEIOUaeiou";
    char *matchLetter;
    matchLetter = strchr(vowel, *word);
    // consonant
    if (matchLetter == NULL){
        // copy the letter except the head
        // length = lenght of string without delimiter
        // cat the head and add ay
        // this will copy the delimater,
        strncpy(bigLatin, word+1, length);
        strncat(bigLatin, word, 1);
        strcat(bigLatin, "ay");
    }
    // vowel
    else {
        // just append "ay"
        strcpy(bigLatin, word);
        strcat(bigLatin, "ay");
    }


    return bigLatin;
}

char *translateSentence(char ***words, int count){
    char *bigLatinSentence;
    int length = 0;
    char *bigLatinWord;

    /* calculate the sum of the length of the words */
    char ***walker = words;
    while (*walker){
        length += strlen(**walker);
        walker++;
    }

    /* allocate space for return string */
    // one space between 2 words
    // numbers of space required = 
    // length of words
    // + (no. of words * of a spaces (1) -1 ) 
    // + delimater
    // + (no. of words * ay (2) )
    int lengthOfResult = length + count + (count * 2);
    bigLatinSentence = (char *)malloc(lengthOfResult);
    // trick to initialize the first memory 
    strcpy(bigLatinSentence, "");

    /* Translate each word */
    int i;
    char *w;
    for (i=0; i<count; i++){
        w = translate(**(words + i));
        strcat(bigLatinSentence, w);
        strcat(bigLatinSentence, " ");
        assert(w != **(words + i));
        free(w);
    }


    /* free memory of big latin words */
    walker = words;
    while (walker != NULL && *walker != NULL){
        free(**walker); 
        free(*walker);
        free(walker); 

        walker++;
    }

    return bigLatinSentence;
}
4

4 回答 4

4

您的代码不必要地复杂,因为您已经进行了如下设置:

  • n: 字数
  • words: 指向可以n+1 char **按顺序保存值的已分配内存
  • words[i]( 0 <= i && i < n):指向分配的内存,可以char *按顺序容纳一个
  • words[n]NULL
  • words[i][0]: 指向为一个单词分配的内存(和以前一样,0 <= i < n)

由于每个都words[i]指向按顺序排列的内容,因此words[i][j]对于某个有效的整数 j 有一个 ...但允许的值j始终为 0,因为那里只有一个char *malloc() 。因此,您可以完全消除这种间接级别,并且只需char **words.

不过,这不是问题。释放循环以与walker相同的开头words,因此它首先尝试释放words[0][0](很好并且可以工作),然后尝试释放words[0](很好并且可以工作),然后尝试释放words(很好并且可以工作,但意味着你不能再访问任何其他words[i]的任何值——i即“存储泄漏”)。然后它递增walker,使其或多或少等同于&words[1]; 但words已经free()d了。

我不在这里使用,而是使用walker带有一些整数的循环i

for (i = 0; words[i] != NULL; i++) {
    free(words[i][0]);
    free(words[i]);
}
free(words);

我还建议删除所有强制转换malloc()calloc()返回值。如果您在执行此操作后收到编译器警告,它们通常意味着以下两种情况之一:

  • 你忘记了#include <stdlib.h>,或者
  • 您正在 C 代码上调用 C++ 编译器。

后者有时会起作用,但会带来痛苦:好的 C 代码是糟糕的 C++ 代码,而好的 C++ 代码不是 C 代码。:-)


编辑:PS:我错过了lengthOfResult@David RF 抓到的一个接一个。

于 2013-07-26T22:44:40.577 回答
3
int lengthOfResult = length + count + (count * 2);

一定是

int lengthOfResult = length + count + (count * 2) + 1; /* + 1 for final '\0' */

while (walker != NULL && *walker != NULL){
    free(**walker); 
    free(*walker);
    /* free(walker); Don't do this, you still need walker */
    walker++;
}
free(words); /* Now */

你有一个泄漏:

int main(void)
{
    ...
    free(result); /* You have to free the return of translateSentence() */
    return 0;
}
于 2013-07-26T22:35:51.527 回答
1

哇,所以我对此很感兴趣,我花了一段时间才弄清楚。

现在想通了,我觉得很傻。

我在 gdb 下运行时注意到的是,在第二次通过在线循环运行时,事情失败了

免费(步行者);

现在为什么会这样。这就是我没有立即看到它而感到愚蠢的地方。当您第一次运行该行时,整个 char*** 指针数组在第二次运行时指向单词(第一次运行时也称为 walker),当您运行该行时,您正试图释放已经释放的记忆。

所以应该是:

while (walker != NULL && *walker != NULL){
    free(**walker); 
    free(*walker);

    walker++;
}

free(words); 

编辑:

我还想指出,您不必在 C 中从 void * 强制转换。

因此,当您调用 malloc 时,您不需要其中的 (char *)。

于 2013-07-26T22:48:48.220 回答
1

在这段代码中:

while (walker != NULL && *walker != NULL){
    free(**walker); 
    free(*walker);
    free(walker); 

    walker++;
}

在释放它之前,您需要检查它**walker不是 NULL。

另外-当您计算需要返回字符串的内存长度时,您会短一个字节,因为您复制每个单词加上一个空格(包括最后一个单词后的空格)加上终止符\0。换句话说,当您将结果复制到 中时bigLatinSentence,您将覆盖一些不属于您的内存。有时你会逃脱,有时你不会......

于 2013-07-26T21:48:13.557 回答