可以理解的是,如果超过缓冲区大小会出错(或创建溢出),但如果12字节缓冲区中只使用了不到12个字节,会发生什么情况?是否可能或者空的后续总是填充0?一个有关的问题:当应用程序尚未使用时,实例化的缓冲区中包含什么内容?
我看了一些在Visual Studio中的小程序,它们似乎附加了0(或空字符),但我不确定这是否是Microsoft实现的,可能在不同的语言/编译器中会有所不同。
可以理解的是,如果超过缓冲区大小会出错(或创建溢出),但如果12字节缓冲区中只使用了不到12个字节,会发生什么情况?是否可能或者空的后续总是填充0?一个有关的问题:当应用程序尚未使用时,实例化的缓冲区中包含什么内容?
我看了一些在Visual Studio中的小程序,它们似乎附加了0(或空字符),但我不确定这是否是Microsoft实现的,可能在不同的语言/编译器中会有所不同。
举个例子(在代码块内,而不是全局范围内):
char data[12];
memcpy(data, "Selbie", 6);
甚至包括这个例子:
char* data = new char[12];
memcpy(data, "Selbie", 6);
在上述两种情况中,data
的前6个字节分别是 S
, e
, l
, b
, i
和 e
。剩余的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 中有编译器标志和内存分配技术,可以保证零初始化内存分配,但这不是可移植的。
char
类型,因为它们不能有陷阱表示或填充,但仍然读取未初始化部分是没有意义或不良代码的情况。 - underscore_dstrncpy(data, "Selbie", 12)
也会用0填充缓冲区的其余部分。 - ilkkachuC++ 有包括全局的、自动的和静态的存储类。变量的初始化取决于其声明方式。
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
}
这里有一些有趣的细节.
考虑一下你的缓冲区,它被填充了零:
[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(或空字符),但我不确定这是否是MS实现,在语言/编译器之间可能会有所不同。
这正是您想象的那样。此时已有人为您执行此操作,但不能保证以后也会发生。它可能是一个编译器标志,附加清理代码。某些版本的MSVC在调试中运行但在发布中不会将新的内存填充为0xCD。还可以是系统安全功能,在将其提供给您的进程之前擦除内存(以便您无法窥探其他应用程序)。始终请记住在重要位置上使用memset
来初始化缓冲区。最后,如果您依赖于新缓冲区包含某个值,请在readme中强制使用特定的编译器标志。
但是清理并不是必要的。您拿了一个12字节长的缓冲区。您用7个字节填充它。然后将其传递到某个地方-您说“这里有7个字节供您使用”。读取缓冲区时,缓冲区的大小与其无关。您期望其他函数读取您所写的内容,而不是尽可能多地读取。实际上,在C中通常无法确定缓冲区的长度。
另外一件事:
可以理解为超过缓冲区会出错(或创建溢出)
这并不是真的,这就是问题所在。这就是为什么它是一个巨大的安全问题:没有错误,并且程序试图继续运行,因此它有时会执行它从未打算执行的恶意内容。因此,我们必须向操作系统添加一堆机制,比如ASLR,这将增加崩溃程序的可能性并减少继续使用损坏内存的可能性。因此,永远不要依赖这些事后防护措施,并自己观察您的缓冲区边界。
main
之前会被初始化为0
。其他数组,无论是具有自动存储的本地值还是使用malloc()
从堆中分配的数组,其内容未指定,将其作为字节读取是可以的,但对于大多数其他类型来说具有未定义的行为。由calloc()
分配的数组被初始化为所有位都为零,这与*初始化为0
*略有不同。 - chqrlie写入缓冲区的一部分不会影响未写入的部分;它将包含之前存在的内容(这自然完全取决于您最初如何获得缓冲区)。
正如其他答案所指出的,静态和全局变量将被初始化为0
,但局部变量将不会被初始化(而是包含先前在堆栈上的任何东西)。 这符合零开销原则:在某些情况下,初始化局部变量将是不必要和不希望的运行时成本,而静态和全局变量作为数据段的一部分在加载时分配。
堆存储的初始化由内存管理器决定,但通常也不会被初始化。
静态持续时间声明的对象(在函数外部声明或使用static
限定符声明)如果没有指定初始化程序,则会被初始化为表示文字零的任何值[即整数零、浮点零或空指针,视情况而定,或包含这些值的结构体或联合体]。如果任何对象(包括自动持续时间的对象)的声明包括初始化程序,则由该初始化程序指定值的部分将按指定设置,其余部分将像静态对象一样清零。
对于没有初始化程序的自动对象,情况有些模糊。例如:
#include <string.h>
unsigned char static1[5], static2[5];
void test(void)
{
unsigned char temp[5];
strcpy(temp, "Hey");
memcpy(static1, temp, 5);
memcpy(static2, temp, 5);
}
标准明确表示,即使复制了未初始化的temp
部分,test
也不会引发未定义行为。至少在C11中,标准文本并不清楚有关static1 [4]
和static2 [4]
值的任何保证,特别是它们是否可能保留不同的值。一个缺陷报告说明标准不打算禁止编译器表现得好像代码已经这样:
unsigned char static1[5]={1,1,1,1,1}, static2[5]={2,2,2,2,2};
void test(void)
{
unsigned char temp[4];
strcpy(temp, "Hey");
memcpy(static1, temp, 4);
memcpy(static2, temp, 4);
}
static1[4]
和static2[4]
保存不同的值。标准未说明是否应该在各种目的下使用的高质量编译器在该函数中表现出这种行为。标准也没有提供指导,如果程序员的意图要求static1[4]
和static2[4]
保持相同的值,但并不关心该值是什么,则该函数应如何编写。 \0
字符。当您使用具有static
持续时间的数组(包括字符数组)进行分配时,所有未使用的条目都将初始化为0。
当您分配一个数组并给它一个明确的初始化程序时,所有未使用的条目都将初始化为0。
当您调用calloc
时,分配的内存将初始化为全零位。
当您调用strncpy
时,目标字符串将使用\0
字符填充到大小为n
。
在所有其他情况下,缓冲区的未使用部分是不可预测的,并且通常包含上次的内容(无论这意味着什么)。特别地,您无法预测具有自动持续时间(即局部于函数并且没有使用static
声明的)的未初始化数组的内容,也无法预测使用malloc
获得的内存的内容。 (在这两种情况下,有些时候内存 tend to start out as all-bits-zero第一次,但您绝对不想依赖此功能。)
strncpy
的好处:我很想点赞,因为它教会了用户一个不太知名的副作用,但也想踩一下,因为它隐含地倡导使用这个容易出错的函数,可惜我不能两者兼顾,所以我将不做任何评价。 - chqrlie这取决于存储类别说明符、您的实现以及其设置。
一些有趣的例子:
- 未初始化的堆栈变量可能被设置为0xCCCCCCCC
- 未初始化的堆变量可能被设置为0xCDCDCDCD
- 未初始化的静态或全局变量可能被设置为0x00000000
- 或者可能是垃圾值。
对此做出任何假设都存在风险。
我认为正确的答案是你应该始终跟踪写入的字符数。与读取和写入等低级函数一样,需要或提供读取或写入的字符数。同样,std::string在其实现中跟踪字符数。
memset
可以用来确保缓冲区被初始化为零。 - TruBlustd::fill
函数实现相同的功能。 - MSalterschar foo[x]={0};
来进行零初始化。 - hanshenrikmalloc()
比calloc()
更有效率,因此在分配内存时它是首选方法,除非需要零初始化。 - TruBlu