如果我向一个12字节的缓冲区写入的字节数少于12,会发生什么情况
What happens if I write less than 12 bytes to a 12 byte buffer?
可以理解,遍历缓冲区会出错(或产生溢出),但如果12字节缓冲区中使用的字节少于12字节,会发生什么?有可能吗?或者空的尾部总是用0填充?正交问题可能会有所帮助:当缓冲区被实例化但尚未被应用程序使用时,缓冲区中包含什么?
我看过Visual Studio中的一些宠物程序,它们似乎附加了0(或null字符),但我不确定这是否是一个可能因语言/编译器而异的MS实现。
举以下例子(在代码块内,而不是全局):
char data[12];
memcpy(data, "Selbie", 6);
甚至这个例子:
char* data = new char[12];
memcpy(data, "Selbie", 6);
在上述两种情况下,data
的前6个字节是S
、e
、l
、b
、i
和e
。data
的其余6个字节被视为"未指定"(可以是任何字节)。
有可能吗?或者空的尾部总是用0填充?
根本不保证。据我所知,唯一能保证零字节填充的分配器是calloc。示例:
char* data = calloc(12,1); // will allocate an array of 12 bytes and zero-init each byte
memcpy(data, "Selbie");
当缓冲区被实例化但尚未被应用程序使用时,缓冲区中包含什么?
从技术上讲,根据最新的C++标准,分配器传递的字节在技术上被视为"未指定"。您应该假设它是垃圾数据(任何东西)。不要对内容做任何假设。
使用Visual Studio的调试版本通常会使用0xcc
或0xcd
值初始化缓冲区,但在发布版本中并非如此。然而,对于Windows和Visual Studio,有一些编译器标志和内存分配技术可以保证零初始化内存分配,但它是不可移植的。
考虑您的缓冲区,填充了零:
[00][00][00][00][00][00][00][00][00][00][00][00]
现在,让我们给它写10个字节。值从1:递增
[01][02][03][04][05][06][07][08][09][10][00][00]
现在,这一次,4次0xFF:
[FF][FF][FF][FF][05][06][07][08][09][10][00][00]
如果12字节缓冲区中使用的字节少于12,会发生什么?有可能吗?或者空的尾部总是用0填充?
你想写多少就写多少,剩下的字节保持不变。
可能有帮助的正交问题:当它被实例化但还没有被应用程序使用?
未指定。以前使用过此内存的程序(或程序的其他部分)可能会留下垃圾。
我看过Visual Studio中的一些宠物程序,它们似乎附加了0(或null字符),但我不确定这是否是一个可能因语言/编译器而异的MS实现。
这正是你所认为的。这次有人为你做了这件事,但不能保证它会再次发生。它可以是一个附加清理代码的编译器标志。某些版本的MSVC在调试时运行,但在发行版中未运行时,用于用0xCD填充新内存。它也可以是一种系统安全功能,在将内存提供给进程之前会擦除内存(这样你就不能监视其他应用程序)。始终记得使用memset
来初始化重要的缓冲区。最后,如果您依赖于新缓冲区来包含某个值,请在自述文件中强制使用特定的编译器标志。
但清洁并不是真正必要的。您需要一个12字节长的缓冲区。您用7个字节填充它。然后你把它传给某个地方——然后你说"这是给你的7个字节"。从缓冲区中读取时,缓冲区的大小是不相关的。你希望其他函数能像你写的一样读取,而不是尽可能多。事实上,在C语言中,通常无法判断缓冲区的长度
附带说明:
可以理解,遍历缓冲区会导致出错(或产生溢出)
没有,这就是问题所在。这就是为什么这是一个巨大的安全问题:没有错误,程序试图继续,所以它有时会执行从未想过的恶意内容。因此,我们不得不在操作系统中添加一系列机制,如ASLR,这将增加程序崩溃的概率,并降低程序继续使用损坏内存的概率。所以,永远不要依赖那些事后想到的警卫,自己观察你的缓冲区边界。
C++具有全局、自动和静态存储类。初始化取决于变量的声明方式。
char global[12]; // all 0
static char s_global[12]; // all 0
void foo()
{
static char s_local[12]; // all 0
char local[12]; // automatic storage variables are uninitialized, accessing before initialization is undefined behavior
}
这里有一些有趣的细节。
程序知道字符串的长度,因为它以null终止符(值为零的字符)结束。
这就是为什么为了在缓冲区中容纳一个字符串,缓冲区必须比字符串中的字符数至少长1个字符,这样它才能容纳字符串加上null终止符。
缓冲区中在此之后的任何空间都保持不变。如果以前那里有数据,它仍然存在。这就是我们所说的垃圾。
仅仅因为你还没有使用过这个空间,就认为这个空间是零填充的,这是错误的,在你的程序达到这个地步之前,你不知道这个特定的内存空间是用来做什么的。应将未初始化的内存视为随机且不可靠的内存来处理。
前面的所有答案都非常好,非常详细,但OP似乎是C编程的新手。因此,我认为真实世界示例可能会有所帮助。
想象一下,你有一个可以装六瓶的纸板饮料架。它一直放在你的车库里,所以它不是六个瓶子,而是车库角落里堆积的各种令人讨厌的东西:蜘蛛、老鼠屋等。
计算机缓冲区在你分配后有点像这样。你不能真正确定里面有什么,你只知道它有多大。
现在,假设你在支架里放了四个瓶子。你的支架没有变大,但你现在知道其中四个空间里有什么了。另外两个空间,连同它们的可疑内容,仍然存在。
计算机缓冲区也是如此。这就是为什么您经常看到bufferSize变量来跟踪缓冲区的使用量。一个更好的名称可能是numberOfBytesUsedInMyBuffer,但程序员往往简洁得令人抓狂。
写入缓冲区的一部分不会影响缓冲区的未写入部分;它将包含预先存在的任何内容(这自然完全取决于您最初是如何获得缓冲区的)。
正如另一个答案所指出的,静态和全局变量将被初始化为0
,但局部变量不会被初始化(而是包含预先在堆栈上的任何内容)。这符合零开销原则:在某些情况下,初始化局部变量将是不必要和不必要的运行时开销,而静态和全局变量在加载时作为数据段的一部分进行分配。
堆存储的初始化由内存管理器选择,但通常也不会初始化。
通常情况下,缓冲区不足并不罕见。分配比需要的更大的缓冲区通常是一种好的做法
当缓冲区比它需要的大时,当缓冲区包含的数据比它分配的大小少时,跟踪有多少数据显然很重要。通常有两种方法:(1)使用一个显式计数,保存在一个单独的变量中,或(2)使用"sentinel"值,例如在C.中标记字符串结尾的