【问题标题】:Printing array of strings produces bad output打印字符串数组会产生错误的输出
【发布时间】:2021-05-17 19:44:29
【问题描述】:

我正在尝试解决一个挑战,但我不知道我的代码出了什么问题!

挑战是:

  • 创建一个将字符串拆分为单词的函数。
  • 分隔符是空格、制表符和换行符。
  • 此函数返回一个数组,其中每个框包含一个由单词表示的字符串地址。此数组的最后一个元素应等于 0,以强调数组的末尾。
  • 您的数组中不能有任何空字符串。得出必要的结论。 不能修改给定的字符串。
  • 注意:唯一允许的函数是malloc()

错误/问题: 我遇到了这个问题,我试图解决它,但我无法确定出了什么问题。 我创建了一个名为 split_whitespaces() 的函数来完成这项工作。 当我在 split_whitespaces 函数中打印字符串数组时,我得到以下输出:

Inside the function:
arr_str[0] = This
arr_str[1] = is
arr_str[2] = just
arr_str[3] = a
arr_str[4] = test!

当我在 main 函数中打印字符串数组时,我得到以下输出:

Inside the main function:
arr_str[0] = @X@?~
arr_str[1] = `X@?~
arr_str[2] = just
arr_str[3] = a
arr_str[4] = test!

我创建了一个函数 word_count 来计算输入字符串中有多少个单词,这样我就可以使用 malloc 和 word_count + 1(空指针)分配内存。

int word_count(char *str) {
    int i;
    int w_count;
    int state;

    i = 0;
    w_count = 0;
    state = 0;
    while (str[i]) {
        if (!iswhitespace(str[i])) {
            if (!state)
                w_count++;
            state = 1;
            i++;
        } else {
            state = 0;
            i++;
        }
    }
    return (w_count);
}

还有一个名为 strdup_w 的函数来模仿 strdup 的行为,但只针对单个单词:

char *strdup_w(char *str, int *index) {
    char *word;
    int len;
    int i;

    i = *index;
    len = 0;
    while (str[i] && !iswhitespace(str[i]))
        len++, i++;;
    word = (char *) malloc(len + 1);
    if (!word)
        return (NULL);
    i = 0;
    while (str[*index]) {
        if (!iswhitespace(str[*index])) {
            word[i++] = str[*index];
            (*index)++;
        } else
            break;
    }
    word[len] = '\0';
    return (word);
}

这是我的完整代码:

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

char **split_whitespaces(char *str);
char *strdup_w(char *str, int *index);
int word_count(char *str);
int iswhitespace(char c);

int main(void) {
    char *str = "This is just a test!";
    char **arr_str;
    int i;

    i = 0;
    arr_str = split_whitespaces(str);
    printf("\nOutside the function:\n");
    while (arr_str[i]) {
        printf("arr_str[%d] = %s\n", i, arr_str[i]);
        i++;
    }
    return (0);
}

char **split_whitespaces(char *str) {
    char **arr_str;
    int i;
    int words;
    int w_i;

    i = 0;
    w_i = 0;
    words = word_count(str);
    arr_str = (char **)malloc(words + 1);
    if (!arr_str)
        return (NULL);
    printf("Inside the function:\n");
    while (w_i < words) {
        while (iswhitespace(str[i]) && str[i])
            if (!str[i++])
                break;
        arr_str[w_i] = strdup_w(str, &i);
        printf("arr_str[%d] = %s\n", w_i, arr_str[w_i]);
        w_i++;
    }
    arr_str[words] = 0;
    return (arr_str);
}

char *strdup_w(char *str, int *index) {
    char *word;
    int len;
    int i;

    i = *index;
    len = 0;
    while (str[i] && !iswhitespace(str[i]))
        len++, i++;;
    word = (char *)malloc(len + 1);
    if (!word)
        return (NULL);
    i = 0;
    while (str[*index]) {
        if (!iswhitespace(str[*index])) {
            word[i++] = str[*index];
            (*index)++;
        } else
            break;
    }
    word[len] = '\0';
    return (word);
}

int word_count(char *str) {
    int i;
    int w_count;
    int state;

    i = 0;
    w_count = 0;
    state = 0;
    while (str[i]) {
        if (!iswhitespace(str[i])) {
            if (!state)
                w_count++;
            state = 1;
            i++;
        } else {
            state = 0;
            i++;
        }
    }
    return (w_count);
}

int iswhitespace(char c) {
    if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
        return (1);
    return (0);
}

对不起,如果有什么问题,这是我第一次尝试寻求帮助。

【问题讨论】:

  • 建议:不要为 C 代码标记 C++,反之亦然。当然,C 和 C++ 程序员之间有很多交叉,你会吸引更多的目光关注这个问题,但并不是所有的眼睛都感兴趣,他们可以像感兴趣的人一样对问题的优点进行投票。
  • split_whitespaces 中,尝试将arr_str = (char **) malloc(words + 1); 更改为arr_str = malloc(sizeof(*arr_str) * (words + 1)); 正如你所拥有的那样,words 是一个 countnot byte length,所以你没有分配足够的空间,所以你有 UB。
  • @CraigEstey 非常感谢!但是看了一些教程,他们说 malloc 接受一个参数,即要分配的内存大小(以字节为单位),这就是我为 5 个字节分配内存的原因!你能告诉我在没有 sizeof() 函数的情况下使用 malloc 的替代方法吗?我会很感激的。
  • 为什么要避开sizeof函数?这没有多大意义
  • @lulle 在挑战/练习中他们告诉我们唯一允许的功能是malloc(),我想这是一种迫使我们自己尝试解决的方法..

标签: c malloc


【解决方案1】:

代码中存在多个问题:

  • arr_str = (char **)malloc(words + 1);中的大小不正确,你必须将元素的数量乘以元素的大小:

      arr_str = malloc(sizeof(*arr_str) * (words + 1));
    
  • 使用后在main() 函数中释放数组是一种很好的方式。

  • 测试while (iswhitespace(str[i]) &amp;&amp; str[i]) 是多余的:如果w_count 计算正确,则不需要测试str[i]。您应该使用strspn() 跳过空格,使用strcspn() 跳过单词字符。

  • if (!str[i++]) break; 在循环内完全是多余的:str[i] 已经过测试并且不为空。

  • while (str[i] &amp;&amp; !iswhitespace(str[i])) len++, i++;; 风格不好。如果循环体中有多个简单语句,请使用大括号。

  • strdup_w 的最后一个循环很复杂,你可以简单地使用memcpy(word, str + *index, len); *index += len;

这是修改后的版本:

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

char **split_whitespaces(const char *str);
char *strdup_w(const char *str, int *index);
int word_count(const char *str);
int iswhitespace(char c);

int main(void) {
    const char *str = "This is just a test!";
    char **arr_str;
    int i;

    arr_str = split_whitespaces(str);
    if (arr_str) {
        printf("\nOutside the function:\n");
        i = 0;
        while (arr_str[i]) {
            printf("arr_str[%d] = %s\n", i, arr_str[i]);
            i++;
        }
        while (i --> 0) {
            free(arr_str[i]);
        }
        free(arr_str);
    }
    return 0;
}

char **split_whitespaces(const char *str) {
    char **arr_str;
    int i;
    int words;
    int w_i;

    i = 0;
    w_i = 0;
    words = word_count(str);
    arr_str = malloc(sizeof(*arr_str) * (words + 1));
    if (!arr_str)
        return NULL;
    printf("Inside the function:\n");
    while (w_i < words) {
        while (iswhitespace(str[i]))
            i++;
        arr_str[w_i] = strdup_w(str, &i);
        if (!arr_str[w_i])
            break;
        printf("arr_str[%d] = %s\n", w_i, arr_str[w_i]);
        w_i++;
    }
    arr_str[words] = NULL;
    return arr_str;
}

char *strdup_w(const char *str, int *index) {
    char *word;
    int len;
    int start;
    int i;

    i = *index;
    start = i;
    while (str[i] && !iswhitespace(str[i])) {
        i++;
    }
    *index = i;
    len = i - start;
    word = malloc(len + 1);
    if (!word)
        return NULL;
    i = 0;
    while (i < len) {
        word[i] = str[start + i];
        i++;
    }
    word[i] = '\0';
    return word;
}

int word_count(const char *str) {
    int i;
    int w_count;
    int state;

    i = 0;
    w_count = 0;
    state = 0;
    while (str[i]) {
        if (!iswhitespace(str[i])) {
            if (!state)
                w_count++;
            state = 1;
        } else {
            state = 0;
        }
        i++;
    }
    return w_count;
}

int iswhitespace(char c) {
    return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
}

【讨论】:

  • 非常感谢您的帮助,我很抱歉,因为我似乎忘记在挑战中提及这一点。唯一允许的功能是mallol(),这就是为什么我避免使用任何其他功能..看了一些教程,他们说 malloc 接受一个参数,即要分配的内存大小(以字节为单位),这就是为什么我分配了 5 个字节的内存!.. 如果你能解释一下,我将不胜感激我为什么它不起作用!
  • @AchrafELKh​​nissi:为 4 个 char 的字符串分配 5 个字节是可以的,因为在 C 类型中,char 的大小根据定义为 1 字节。但是,在分配char * 的数组时,您必须将条目数乘以指针的大小,sizeof(char*),这通常是 4 或 8 个字节,具体取决于目标系统。 arr_str 是指向char 的指针,因此sizeof(*arr_str)sizeof(char *)
  • 哦..现在我明白了。我非常感谢你。我真的很感谢你的帮助,我花了最后几个小时试图找出我做错了什么!非常感谢。
  • 是否允许使用for 循环而不是while 循环?在一个位置使用for 循环结合索引初始化、测试和索引增量是一个好习惯:for (i = 0; arr_str[i]; i++) { printf("arr_str[%d] = %s\n", i, arr_str[i]); }
  • 没有for 循环。即使printf() 也是不允许的,我们使用系统调用函数write() 来打印一个字符串。但我使用printf() 因为我只是用它测试我的输出。
【解决方案2】:

来自我的顶级评论...

split_whitespaces,尝试更改:

arr_str = (char **) malloc(words + 1);

进入:

arr_str = malloc(sizeof(*arr_str) * (words + 1));

正如你所拥有的,words 是一个计数,而不是一个字节长度,所以你没有分配足够的空间,所以你有 UB。


更新:

但是看了一些教程,他们说 malloc 接受一个参数,即要分配的内存大小(以字节为单位),这就是为什么我分配了 5 个字节的内存!你能告诉我在没有sizeof() 功能的情况下使用malloc 的替代方法吗?我会很感激的。 ——Achraf EL Khnissi

真的没有 clean 方法来指定这个没有 sizeof

sizeof 不是一个函数[尽管有语法]。它是一个编译器指令。它“返回”其参数占用的字节数作为编译时间常数。

如果我们有char buf[5];,则有5个字节,所以sizeof(buf)[或sizeof buf]是5。

如果我们有:int buf[5];,则有 5 个元素,每个大小为 int,[通常] 4 个字节,因此总空间(以字节为单位)为 sizeof(int) * 54 * 5 是 20。

但是,int 可能因架构而异。在 Intel 8086 上 [大约 1980 年代],int 是 2 个字节(即 16 位)。所以,上面的4 * 5 是错误的。应该是2 * 5

如果我们使用sizeof(int),那么无论架构如何,sizeof(int) * 5 都可以工作。

类似地,在 32 位机器上,指针 [通常] 为 32 位。所以,sizeof(char *) 是 4 [字节]。在 64 位机器上,指针是 64 位,即 8 个字节。所以,sizeof(char *) 是 8。

因为arr_str 是:char **arr_str,我们本可以这样做:

arr_str = malloc(sizeof(char *) * (words + 1));

但是,如果 arr_str 的定义曾经改变(例如)struct string *arr_str;,那么我们刚刚所做的将中断/失败 如果我们 忘记把作业改成:

arr_str = malloc(sizeof(struct string) * (words + 1));

所以,做:

arr_str = malloc(sizeof(*arr_str) * (words + 1));

是编写更简洁代码的首选惯用方式。更多语句会自动调整,无需手动查找所有受影响的代码行。


更新 #2:

您可能只是添加了删除 (char **) 演员表的原因 :) -- chqrlie

请注意,我删除了 (char **) 演员表。见:Do I cast the result of malloc?

这只是添加了额外/不必要的“东西”,因为 void * 的返回值 malloc 可以分配给 任何 类型的指针。

如果我们忘记了:#include &lt;stdlib.h&gt;,则malloc 将有没有函数原型,因此编译器会将返回类型默认为int

如果没有强制转换,编译器会在语句上发出错误[这是我们想要的]。

使用演员,这个动作在编译时被屏蔽 [或多或少]。在 64 位机器上,编译器将使用被截断为 32 位的值 [因为它认为 malloc 返回一个 32 位值] 而不是 malloc 的完整 64 位返回值.

这种截断是“沉默的杀手”。应该标记为编译时错误的内容会产生更难调试的运行时错误(可能是段错误或其他 UB)。

【讨论】:

  • 好书!彻底而清晰。您可能只是添加了删除 (char **) 演员表的原因:)
  • 老实说,我对发布它持怀疑态度,但现在我真的很高兴我决定这样做!我学到了很多。但正如@chqrlie 先生所说,为什么(char *) 而不是(char **)
  • @AchrafELKh​​nissi:我指的是arr_str = (char **) malloc(words + 1); 中的(char **) 强制转换在C 中不需要这种强制转换,因为void 指针在赋值时会隐式转换为其他指针类型(两种方式)。避免大小错误计算的一个有用习惯用法是始终将目标指针指向的类型用作 arr_str = malloc(sizeof(*arr_str) * (words + 1)); 并省略强制转换。
猜你喜欢
  • 2020-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多