如果我向 12 字节缓冲区写入少于 12 字节的内容会怎样?
What happens if I write less than 12 bytes to a 12 byte buffer?
可以理解,遍历缓冲区会出错(或造成溢出),但是如果 12 字节缓冲区中使用的字节少于 12 字节会怎样?有可能还是空尾总是用0填充?可能有帮助的正交问题:当缓冲区被实例化但尚未被应用程序使用时,缓冲区中包含什么?
我看过 Visual Studio 中的一些宠物程序,它们似乎附加了 0(或空字符),但我不确定这是否是一个 MS 实现,可能因语言/编译器。
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
}
一些有趣的细节here。
看下面的例子(在一个代码块内,不是全局的):
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 个字节被认为是 "unspecified"(可以是任何东西)。
Is it possible or does the empty trailing always fill with 0s?
完全不能保证。据我所知,唯一保证零字节填充的分配器是 calloc。示例:
char* data = calloc(12,1); // will allocate an array of 12 bytes and zero-init each byte
memcpy(data, "Selbie");
what is contained in a buffer when it is instantiated but not used by the application yet?
从技术上讲,根据最新的 C++ 标准,分配器传送的字节在技术上被视为 "unspecified"。您应该假设它是垃圾数据(任何东西)。对内容不做任何假设。
使用 Visual Studio 的调试构建通常会使用 0xcc
或 0xcd
值初始化缓冲区,但在发布构建中并非如此。但是,Windows 和 Visual Studio 有编译器标志和内存分配技术,您可以在其中保证零初始化内存分配,但它不可移植。
写入缓冲区的一部分不会影响缓冲区未写入的部分;它将包含之前存在的任何内容(这自然完全取决于您首先如何获得缓冲区)。
正如另一个答案所指出的,静态和全局变量将被初始化为 0
,但局部变量将不会被初始化(而是包含预先在堆栈中的任何内容)。这与零开销原则保持一致:在某些情况下,初始化局部变量是不必要和不需要的 运行 时间成本,而静态和全局变量是在加载时作为数据的一部分分配的段.
堆存储的初始化由内存管理器选择,但一般情况下也不会初始化。
程序知道字符串的长度,因为它以空终止符(值为零的字符)结尾。
这就是为什么要在缓冲区中容纳字符串,缓冲区必须至少比字符串中的字符数长 1 个字符,这样它才能容纳字符串加上空终止符.
缓冲区中之后的任何 space 保持不变。如果以前那里有数据,它仍然在那里。这就是我们所说的垃圾。
仅仅因为你还没有使用它就假设这个space是零填充是错误的,你不知道在你的程序之前那个特定的内存space是用来做什么的到了那个地步。应处理未初始化的内存,就好像其中的内容是随机且不可靠的。
考虑您的缓冲区,其中填充了零:
[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]
what happens if there are less than 12 bytes used in a 12 byte buffer? Is it possible or does the empty trailing always fill with 0s?
你写多少就写多少,剩下的字节不变
Orthogonal question that may help: what is contained in a buffer when
it is instantiated but not used by the application yet?
未指定。预计以前使用此内存的程序(或程序的其他部分)留下的垃圾。
I have looked at a few pet programs in Visual Studio and it seems that they are appended with 0s (or null characters) but I am not sure if this is a MS implementation that may vary across language/ compiler.
这正是你想的那样。这次有人为你做了,但没有保证运行它会再次发生。它可能是附加清理代码的编译器标志。某些版本的 MSVC 在调试 运行 时使用 0xCD 填充新内存,但在发布时不这样做。它也可以是一种系统安全功能,可以在将内存提供给您的进程之前擦除内存(这样您就无法监视其他应用程序)。永远记得在重要的地方使用 memset
来初始化你的缓冲区。最后,如果您依赖新缓冲区来包含特定值,则在自述文件中强制使用特定的编译器标志。
但是清洁并不是必须的。您使用一个 12 字节长的缓冲区。你用 7 个字节填充它。然后你将它传递到某个地方 - 然后你说 "here is 7 bytes for you"。从中读取时,缓冲区的大小无关紧要。您希望其他函数尽可能多地读取您编写的内容,而不是尽可能多地读取。事实上,在 C 中,通常无法判断缓冲区有多长。
附注:
Understandably, going over a buffer errors out (or creates an overflow)
不是,就是这个问题。这就是为什么它是一个巨大的安全问题:没有错误并且程序试图继续,所以它有时会执行它从未打算执行的恶意内容。所以我们不得不向 OS 添加一堆机制,比如 ASLR,这将增加程序崩溃的可能性并降低它继续使用损坏的内存的可能性。所以,永远不要依赖那些事后才想到的守卫,自己观察你的缓冲区边界。
一般来说,缓冲区未满的情况并不少见。分配比实际需要更大的缓冲区通常是一种很好的做法。 (尝试始终计算确切的缓冲区大小是常见的错误来源,而且通常是在浪费时间。)
当缓冲区比需要的大时,当缓冲区包含的数据少于其分配的大小时,跟踪 中有多少数据显然很重要。一般来说,有两种方法可以做到这一点:(1) 使用显式计数,保存在单独的变量中,或 (2) 使用 "sentinel" 值,例如标记结束的 [=10=]
字符C 中的字符串。
但是问题来了,如果不是所有缓冲区都在使用中,未使用的条目包含什么?
一个答案当然是没关系。这就是 "unused" 的意思。您关心使用的条目的值,这些值由您的计数或您的标记值计算。您不关心未使用的值。
基本上有四种情况可以预测缓冲区中未使用条目的初始值:
当你分配一个持续时间为static
的数组(包括字符数组)时,所有未使用的条目都被初始化为0。
当你分配一个数组并给它一个显式初始化器时,所有未使用的条目都被初始化为 0。
当你调用calloc
时,分配的内存被初始化为all-bits-0。
当您调用 strncpy
时,目标字符串会用 [=10=]
个字符填充到大小 n
。
在所有其他情况下,缓冲区未使用的部分是不可预测的,并且通常包含它们上次所做的一切(无论那意味着什么)。特别是,您无法预测具有自动持续时间的未初始化数组的内容(即函数的局部数组且未使用 static
声明),并且您无法预测使用 [= 获得的内存内容17=]。 (有时,在这两种情况下,内存往往会在第一次开始时全为零,但您绝对不想依赖于此。)
之前的所有答案都非常好而且非常详细,但是 OP 似乎是 C 编程的新手。所以,我认为 真实世界 示例可能会有所帮助。
想象一下,您有一个纸板饮料架,可以容纳六个瓶子。它一直放在你的车库里,所以它不是六个瓶子,而是包含各种堆积在车库角落的令人讨厌的东西:蜘蛛、老鼠屋等。
计算机缓冲区在分配后有点像这样。你不能真正确定里面是什么,你只知道它有多大。
现在,假设您在支架上放了四个瓶子。你的持有人没有改变大小,但你现在知道四个空间里有什么。其他两个空间及其可疑内容仍然存在。
计算机缓冲区也是如此。这就是为什么您经常看到一个 bufferSize 变量来跟踪有多少缓冲区正在使用。更好的名称可能是 numberOfBytesUsedInMyBuffer,但程序员往往会非常简洁。
这取决于存储 class 说明符、您的实现及其设置。
一些有趣的例子:
- 未初始化的堆栈变量可以设置为 0xCCCCCCCC
- 未初始化的堆变量可以设置为 0xCDCDCDCD
- 未初始化的静态或全局变量可以设置为 0x00000000
- 或者它可能是垃圾。
对此做出任何假设都是有风险的。
没有指定初始值设定项的静态持续时间声明对象(在函数外部声明的对象,或带有 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);
}
标准明确指出 test
不会调用未定义的行为,即使它复制了 temp
的未初始化部分。标准的文本,至少从 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]
保持相同的值,但不关心该值是什么,则该标准也没有提供关于如何编写函数的指导。
我认为正确的答案是您应该始终跟踪写入了多少个字符。
与读取和写入等低级功能一样,需要或给出读取或写入的字符数。以同样的方式std::string跟踪其实现中的字符数
可以理解,遍历缓冲区会出错(或造成溢出),但是如果 12 字节缓冲区中使用的字节少于 12 字节会怎样?有可能还是空尾总是用0填充?可能有帮助的正交问题:当缓冲区被实例化但尚未被应用程序使用时,缓冲区中包含什么?
我看过 Visual Studio 中的一些宠物程序,它们似乎附加了 0(或空字符),但我不确定这是否是一个 MS 实现,可能因语言/编译器。
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
}
一些有趣的细节here。
看下面的例子(在一个代码块内,不是全局的):
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 个字节被认为是 "unspecified"(可以是任何东西)。
Is it possible or does the empty trailing always fill with 0s?
完全不能保证。据我所知,唯一保证零字节填充的分配器是 calloc。示例:
char* data = calloc(12,1); // will allocate an array of 12 bytes and zero-init each byte
memcpy(data, "Selbie");
what is contained in a buffer when it is instantiated but not used by the application yet?
从技术上讲,根据最新的 C++ 标准,分配器传送的字节在技术上被视为 "unspecified"。您应该假设它是垃圾数据(任何东西)。对内容不做任何假设。
使用 Visual Studio 的调试构建通常会使用 0xcc
或 0xcd
值初始化缓冲区,但在发布构建中并非如此。但是,Windows 和 Visual Studio 有编译器标志和内存分配技术,您可以在其中保证零初始化内存分配,但它不可移植。
写入缓冲区的一部分不会影响缓冲区未写入的部分;它将包含之前存在的任何内容(这自然完全取决于您首先如何获得缓冲区)。
正如另一个答案所指出的,静态和全局变量将被初始化为 0
,但局部变量将不会被初始化(而是包含预先在堆栈中的任何内容)。这与零开销原则保持一致:在某些情况下,初始化局部变量是不必要和不需要的 运行 时间成本,而静态和全局变量是在加载时作为数据的一部分分配的段.
堆存储的初始化由内存管理器选择,但一般情况下也不会初始化。
程序知道字符串的长度,因为它以空终止符(值为零的字符)结尾。
这就是为什么要在缓冲区中容纳字符串,缓冲区必须至少比字符串中的字符数长 1 个字符,这样它才能容纳字符串加上空终止符.
缓冲区中之后的任何 space 保持不变。如果以前那里有数据,它仍然在那里。这就是我们所说的垃圾。
仅仅因为你还没有使用它就假设这个space是零填充是错误的,你不知道在你的程序之前那个特定的内存space是用来做什么的到了那个地步。应处理未初始化的内存,就好像其中的内容是随机且不可靠的。
考虑您的缓冲区,其中填充了零:
[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]
what happens if there are less than 12 bytes used in a 12 byte buffer? Is it possible or does the empty trailing always fill with 0s?
你写多少就写多少,剩下的字节不变
Orthogonal question that may help: what is contained in a buffer when it is instantiated but not used by the application yet?
未指定。预计以前使用此内存的程序(或程序的其他部分)留下的垃圾。
I have looked at a few pet programs in Visual Studio and it seems that they are appended with 0s (or null characters) but I am not sure if this is a MS implementation that may vary across language/ compiler.
这正是你想的那样。这次有人为你做了,但没有保证运行它会再次发生。它可能是附加清理代码的编译器标志。某些版本的 MSVC 在调试 运行 时使用 0xCD 填充新内存,但在发布时不这样做。它也可以是一种系统安全功能,可以在将内存提供给您的进程之前擦除内存(这样您就无法监视其他应用程序)。永远记得在重要的地方使用 memset
来初始化你的缓冲区。最后,如果您依赖新缓冲区来包含特定值,则在自述文件中强制使用特定的编译器标志。
但是清洁并不是必须的。您使用一个 12 字节长的缓冲区。你用 7 个字节填充它。然后你将它传递到某个地方 - 然后你说 "here is 7 bytes for you"。从中读取时,缓冲区的大小无关紧要。您希望其他函数尽可能多地读取您编写的内容,而不是尽可能多地读取。事实上,在 C 中,通常无法判断缓冲区有多长。
附注:
Understandably, going over a buffer errors out (or creates an overflow)
不是,就是这个问题。这就是为什么它是一个巨大的安全问题:没有错误并且程序试图继续,所以它有时会执行它从未打算执行的恶意内容。所以我们不得不向 OS 添加一堆机制,比如 ASLR,这将增加程序崩溃的可能性并降低它继续使用损坏的内存的可能性。所以,永远不要依赖那些事后才想到的守卫,自己观察你的缓冲区边界。
一般来说,缓冲区未满的情况并不少见。分配比实际需要更大的缓冲区通常是一种很好的做法。 (尝试始终计算确切的缓冲区大小是常见的错误来源,而且通常是在浪费时间。)
当缓冲区比需要的大时,当缓冲区包含的数据少于其分配的大小时,跟踪 中有多少数据显然很重要。一般来说,有两种方法可以做到这一点:(1) 使用显式计数,保存在单独的变量中,或 (2) 使用 "sentinel" 值,例如标记结束的 [=10=]
字符C 中的字符串。
但是问题来了,如果不是所有缓冲区都在使用中,未使用的条目包含什么?
一个答案当然是没关系。这就是 "unused" 的意思。您关心使用的条目的值,这些值由您的计数或您的标记值计算。您不关心未使用的值。
基本上有四种情况可以预测缓冲区中未使用条目的初始值:
当你分配一个持续时间为
static
的数组(包括字符数组)时,所有未使用的条目都被初始化为0。当你分配一个数组并给它一个显式初始化器时,所有未使用的条目都被初始化为 0。
当你调用
calloc
时,分配的内存被初始化为all-bits-0。当您调用
strncpy
时,目标字符串会用[=10=]
个字符填充到大小n
。
在所有其他情况下,缓冲区未使用的部分是不可预测的,并且通常包含它们上次所做的一切(无论那意味着什么)。特别是,您无法预测具有自动持续时间的未初始化数组的内容(即函数的局部数组且未使用 static
声明),并且您无法预测使用 [= 获得的内存内容17=]。 (有时,在这两种情况下,内存往往会在第一次开始时全为零,但您绝对不想依赖于此。)
之前的所有答案都非常好而且非常详细,但是 OP 似乎是 C 编程的新手。所以,我认为 真实世界 示例可能会有所帮助。
想象一下,您有一个纸板饮料架,可以容纳六个瓶子。它一直放在你的车库里,所以它不是六个瓶子,而是包含各种堆积在车库角落的令人讨厌的东西:蜘蛛、老鼠屋等。
计算机缓冲区在分配后有点像这样。你不能真正确定里面是什么,你只知道它有多大。
现在,假设您在支架上放了四个瓶子。你的持有人没有改变大小,但你现在知道四个空间里有什么。其他两个空间及其可疑内容仍然存在。
计算机缓冲区也是如此。这就是为什么您经常看到一个 bufferSize 变量来跟踪有多少缓冲区正在使用。更好的名称可能是 numberOfBytesUsedInMyBuffer,但程序员往往会非常简洁。
这取决于存储 class 说明符、您的实现及其设置。
一些有趣的例子:
- 未初始化的堆栈变量可以设置为 0xCCCCCCCC
- 未初始化的堆变量可以设置为 0xCDCDCDCD
- 未初始化的静态或全局变量可以设置为 0x00000000
- 或者它可能是垃圾。
对此做出任何假设都是有风险的。
没有指定初始值设定项的静态持续时间声明对象(在函数外部声明的对象,或带有 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);
}
标准明确指出 test
不会调用未定义的行为,即使它复制了 temp
的未初始化部分。标准的文本,至少从 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]
保持相同的值,但不关心该值是什么,则该标准也没有提供关于如何编写函数的指导。
我认为正确的答案是您应该始终跟踪写入了多少个字符。 与读取和写入等低级功能一样,需要或给出读取或写入的字符数。以同样的方式std::string跟踪其实现中的字符数