我刚开始进行C编程,我有一个初学者问题:
int main(int argc, char *argv[])
{
char *a=malloc(1*sizeof(char));
a[0]='a';
a[1]='b';
a[2]='c';
printf("%c\n",a[0]);
printf("%c\n",a[1]);
printf("%c\n",a[2]);
printf("%s\n",a);
return 0;
}
所以我想通过逐个输入字符来创建一个由未知长度字组成的字符串。因为我不知道单词的长度所以我只使用malloc。我计划先为一次性字符分配内存,然后在输入新字符时使用reallocate为下一个字符添加新空间。但是在我malloc(1 * sizeof(char))之后,我应该发现我可以在字符串中添加多个字符,为什么会发生这种情况?做正确的方法是什么?
感谢大家花时间阅读我的长篇问题:)
答案 0 :(得分:10)
为什么会发生这种情况"你的意思是,
允许,因为C直接访问内存;这是它的力量的一部分。在让您 之前,很少检查您尝试做的事情。这就是你需要小心的原因。
为什么不受惩罚"崩溃,而不是立即,也许永远不会?因为在该区域并不总是禁止写入(内存保护是面向页面的)。让我们说当你分配一个内存区域时,它被分成1000字节的页面。然后,如果您分配50个字节,底层硬件将解锁1000个字节。它无法解锁较小的区域。所以你"可以"写入所有这1000个字节而不会导致保护错误。
现在内存管理器必须跟踪数据 的位置,因此它有自己的结构,而且通常"页面"记忆。因此,当您请求50个字节时,软件内存管理器可能实际分配256个。然后,如果您将这50个字节重新分配()到100,您将看到指针没有改变。如果你realloc()那些257字节,指针 更改 - 内存管理器不能将该块扩大到257字节,所以它标记它是空闲的,并从硬件分配512块在其他地方。如果然后alloc()42个字节,您可能会发现它的指针与之前指向100字节缓冲区的地址相同。
有时候,一些调试库不仅会分配一个区域,而且还会“#34; guard"它与金丝雀。你问50个字节,库分配66并在66个内部返回一个8字节的指针。它用已知值填充前8个字节和后8个字节。时不时会检查价值是否仍然存在;如果不是,则会发出软崩溃,警告您溢出(或下溢)缓冲区。
在您的示例中,没有此类保护,您可以在超出分配范围的额外区域中书写。但是很有可能该区域将在以后使用,并被覆盖:也许,如果你这样做
foo = malloc(20);
strcpy(foo, "string ... 30 bytes long");
bar = malloc(20); // ^20th byte
strcpy(bar, "hello world");
然后打印foo,你得到" string ... 3hello world"。或者"字符串...... [垃圾]你好世界"。通过编写跟随foo的bar,您覆盖了存储数据的区域。
然后,如果你从未在 bar 中写任何内容,那么该程序可能会起作用并且永远不会抱怨。
然后你在另一个平台或不同的库上编译,一个已经工作多年的程序突然崩溃了。欢迎来到未定义行为的世界。
有几个图书馆和工具可以解决这类问题 - 一个非常好的工具是valgrind。
// I initialize the pointer to NULL. If I just declared the pointer,
// its initial value might be anything. This way, I reduce the random
// element in my program. Makes no difference... except that one time
// when it does, and will save your bacon.
char *pwd = NULL;
// Every malloc and realloc MUST check that it did not return NULL,
// meaning an error occurred. Even for small memory blocks.
if (NULL === (pwd = malloc(200))) {
// Handle out of memory error
}
strcpy(pwd, "Squeamish Ossifrage"):
// ... do something with pwd
// ...we're done. If we just freed this area, its contents would remain
// available *and* the pointer would still point to it. so this works:
/*
free(pwd);
printf("The secret word is %s\n", pwd);
...but might explode at any moment.
*/
// pwd contains sensitive data, so we first zero it, and this requires
// remembering the actual size of the allocated block. Here, 200.
memset(pwd, 0, 200);
// Now we free the area pointed to by the pointer. Then we also
// erase the pointer.
free(pwd); pwd = NULL;
通过在同一行写入free和NULL,我可以运行
grep 'free\\s*(' | grep -v "NULL;"
找到free()没有NULL赋值的所有行,并将这些行标记为可能需要改进。
现在如果我在释放它之后使用pwd,它将永远不会工作,这会从执行中删除进一步的随机性。
答案 1 :(得分:8)
这种情况正在发生,因为你被允许这样做,但它是未定义的行为。这称为缓冲区溢出,这是一种危险的编程情况。您应该分配超过1个字节的内存,并跟踪字符串的长度。到达已分配空间的末尾后,您可以调用LIMIT 1
将内存块重新分配到更大的大小。
答案 2 :(得分:0)
int main(/*dont write what you dont use*/)
{
char *a=malloc(4/*at least*/*sizeof(char));
a[0]='a';
a[1]='b';
a[2]='c';
a[3]='\0';
//or strcpy(a, "abc");
printf("%c\n",a[0]);
printf("%c\n",a[1]);
printf("%c\n",a[2]);
printf("%s\n",a);
free(a);
return 0;
}
记住:当你写“?”编译器获取
{?,'\ 0'}
'\ 0'是字符串结尾的char。