【问题标题】:Dynamically increasing C string's size动态增加 C 字符串的大小
【发布时间】:2021-08-12 08:07:07
【问题描述】:

我目前正在创建一个程序来捕获用户的按键并将它们存储在一个字符串中。我希望存储按键的字符串是动态的,但我遇到了一个问题。 我当前的代码如下所示:

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

typedef struct Foo {
  const char* str;
  int size;
} Foo;

int main(void)
{
  int i;
  Foo foo;
  foo.str = NULL;
  foo.size = 0;

  for (;;) {
    for (i = 8; i <= 190; i++) {
      if (GetAsyncKeyState(i) == -32767) { // if key is pressed
        foo.str = (char*)realloc(foo.str, (foo.size + 1) * sizeof(char)); // Access violation reading location xxx
        sprintf(foo.str, "%s%c", foo.str, (char)i);
        foo.size++;
      }
    }
  }

  return 0;
}

任何帮助将不胜感激,因为我没有任何想法了。 :(
我是否也应该动态分配 Foo 对象?

【问题讨论】:

  • sprintf(foo.str, "%s%c", foo.str, (char)i);。问问你自己 - 调用 foo.str 时的内容是什么?如果答案每次都不是“有效的 NUL 终止的 C 字符串”,则行为未定义。而且您不必看得太远 - 甚至只需在第一次通话时进行该练习。
  • 附带说明:正如GetAsyncKeyState 的文档中所述,您不应该使用返回值的最低有效位,因为它不可靠并且仅存在于向后兼容16 位 Windows。
  • 附带说明:在busy-wait 中使用GetAsyncKeyState 并不是等待用户输入的好方法,因为这会导致一个CPU 100% 使用CPU,从而阻止其他线程和使用该 CPU 的进程。也会增加耗电量。如果您正在编写一个图形 Windows 应用程序,您应该创建一个适当的消息循环。如果您正在编写 Windows 控制台应用程序,则应改用 ReadConsoleInput
  • 旁白:如果realloc() 失败,您会泄漏内存。始终使用 tmp 指针来验证 realloc() 的结果,并且在成功时将其分配回您的主指针(失败时您的主指针仍然存在,然后由您决定如何处理错误)

标签: c string memory-leaks dynamic-memory-allocation


【解决方案1】:
  1. const char *str - 你声明指向const char 的指针。您不能在调用 UB 时写入引用的对象
  2. 您使用sprintf 只是为了添加char。这毫无意义。
  3. 结构中不需要指针。

您需要设置编译器选项来编译**为 C 语言”而不是 C++

我会做一些不同的方式:

typedef struct Foo {
    size_t size;
    char str[1];
} Foo;

Foo *addCharToFoo(Foo *f, char ch);
{
    if(f)
    {
        f = realloc(f, sizeof(*f) + f -> size);
    }
    else
    {
        f = realloc(f, sizeof(*f) + 1);
        if(f) f-> size = 0
    }
    if(f) //check if realloc did not fail
    {
        f -> str[f -> size++] = ch;
        f -> str[f -> size] = 0;
    }
    return f;
}

main

int main(void)
{
    int i;
    Foo *foo = NULL, *tmp;

    for (;;) 
    {
        for (i = 8; i <= 190; i++) 
        {
            if (GetAsyncKeyState(i) == -32767) { // if key is pressed
            if((tmp = addCharToFoo(f, i))
            {
                foo = tmp;
            }
            else
            /* do something - realloc failed*/
            }
        }
    }

    return 0;
}

【讨论】:

  • 当我尝试编写 char str[]; 时,我的 IDE 给了我一个错误(不允许不完整的类型):/
  • @czarson Microsodt。他们仍然没有实施 1980 年的变化
  • 很高兴,如果我将数组声明为结构的最后一个元素,它不会产生错误
【解决方案2】:

sprintf(foo.str, "%s%c", foo.str, (char)i); 格式错误:第一个参数不能是 const char *。您应该会看到编译器错误消息。

修复此问题后(使str 成为char *),则行为未定义,因为%s 读取的源内存与目标重叠。

相反,您需要使用其他方法来附加不涉及重叠读写的字符(例如,使用[ ] 运算符来写入字符并且不要忘记空终止)。

【讨论】:

    【解决方案3】:

    首先,为了处理好事情,你需要定义

    typedef struct Foo {
        char* str;
        int size
    } Foo;
    

    否则,Foo 正确地进行变异真的很烦人 - 您通过在 realloc 调用之后以任何方式修改 foo-&gt;str 来调用未定义的行为。

    段错误实际上是由sprintf(foo.str, "%s%c", foo.str, (char)i);引起的,而不是对realloc的调用。 foo.str 通常不会以空值结尾。

    事实上,您调用sprintf 就是在重复工作。 realloc 已经复制了之前f.str 中的所有字符,所以你所要做的就是通过添加单个字符

    f.str[size] = (char) i;
    

    编辑以回复评论:

    如果我们想一起追加到字符串(或者更确切地说,两个 Foo),我们可以这样做:

    void appendFoos(Foo* const first, const Foo* const second) {
        first->str = realloc(first->str, (first->size + second->size) * (sizeof(char)));
        memcpy(first->str + first->size, second->str, second->size);
        first->size += second->size;
    }
    

    appendFoos 函数通过在 first 上附加 second 来修改它。

    在整个代码中,我们将Foos 保留为非空终止。但是,要转换为字符串,您必须在读取所有其他字符后添加最后一个空字符。

    【讨论】:

    • 它仍然不是 C 字符串
    • @0___________ 你是什么意思?我们不是想把它变成一个 C 字符串。
    • @MarkSaving: "We're not trying to make it a C string" -- 这句话似乎不正确。引用问题:"I'm currently creating a program that captures user's keypresses and stores them in a string."
    • @0___________ 这确实是可能的,因为f.str 指向size + 1 字节的分配。演员表可能不是必需的,但绝对有利于清晰。
    • @0___________ 这如何引起未定义的行为?
    猜你喜欢
    • 2018-12-29
    • 2013-01-13
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    相关资源
    最近更新 更多