动态增加 C 字符串的大小
Posted
技术标签:
【中文标题】动态增加 C 字符串的大小【英文标题】:Dynamically increasing C string's size 【发布时间】: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()
的结果,并且仅在成功时将其分配回您的主指针(失败时您的主指针仍然存在,然后由您决定如何处理错误)
【参考方案1】:
const char *str
- 你声明指向const char
的指针。您不能在调用 UB 时写入引用的对象
您使用sprintf
只是为了添加char
。这毫无意义。
结构中不需要指针。
您需要设置编译器选项来编译**为 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->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___________ 这如何引起未定义的行为?以上是关于动态增加 C 字符串的大小的主要内容,如果未能解决你的问题,请参考以下文章
通过动态分配创建数组后,在C中通过realloc改变内存大小时出现问题