如果我将少于12个字节的内容写入12个字节的缓冲区会怎样?

时间:2018-09-17 02:00:00

标签: c++ c memory-management buffer

可以理解,越过缓冲区会出错(或产生溢出),但是如果12字节缓冲区中使用的少于12个字节会发生什么呢?有可能还是空的尾随总是填充0?正交问题可能会有所帮助:实例化缓冲区时,缓冲区中包含哪些内容,但尚未被应用程序使用?

我查看了Visual Studio中的一些宠物程序,它们似乎都附加了0(或空字符),但是我不确定这是否是MS实现,可能会因语言/编译器而异。

10 个答案:

答案 0 :(得分:17)

采用以下示例(在代码块内,而不是全局):

char data[12];
memcpy(data, "Selbie", 6);

甚至这个例子:

char* data = new char[12];
memcpy(data, "Selbie", 6);

在上述两种情况下,data的前6个字节为Selbiedata的其余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进行调试的版本通常会使用具有0xcc0xcd值的缓冲区进行初始化,但在发行版本中并非如此。但是,对于Windows和Visual Studio,有编译器标志和内存分配技术,可以保证零初始化内存分配,但它不可移植。

答案 1 :(得分:11)

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

答案 2 :(得分:11)

考虑您的缓冲区,该缓冲区用零填充:

[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初始化缓冲区。最终,如果您依靠新鲜缓冲区包含某个值,则在自述文件中使用某些编译器标志来强制执行。

但是清洁并不是必须的。您需要一个12字节长的缓冲区。您用7个字节填充它。然后将其传递到某个地方-然后说“这里有7个字节供您使用”。从缓冲区读取时,缓冲区的大小无关紧要。您希望其他功能读取尽可能多的内容,而不是尽可能多的内容。实际上,在C语言中,通常无法确定缓冲区的长度。

和旁注:

  

可以理解,遍历缓冲区错误(或产生溢出)

不是,那是问题所在。这就是为什么它是一个巨大的安全问题:没有错误,程序会尝试继续运行,因此它有时会执行本来不是本意的恶意内容。因此,我们必须向OS添加一堆机制,例如ASLR,这将增加崩溃程序的可能性,并降低其因内存损坏而继续发生的可能性。因此,永远不要依赖那些事后的后卫,自己观察缓冲区边界。

答案 3 :(得分:4)

程序知道字符串的长度,因为它以空终止符(零值字符)结尾。

这就是为什么要在缓冲区中容纳字符串,缓冲区必须比字符串中的字符数至少长1个字符,以便它也可以容纳字符串和空终止符。 / p>

该缓冲区之后的任何空间均保持不变。如果以前有数据,那么它仍然在那里。这就是我们所说的垃圾。

仅仅因为您尚未使用该空间而认为该空间为零是错误的,在您的程序到达这一点之前,您不知道该特定存储空间的用途。未初始化的内存应被视为随机且不可靠。

答案 4 :(得分:3)

所有先前的答案都非常好而且非常详细,但是OP似乎是C编程的新手。因此,我认为真实世界示例可能会有所帮助。

想象一下,您有一个可容纳六个瓶子的纸板饮料架。它一直坐在您的车库中,所以它没有六个瓶子,而是在车库的各个角落中积聚了各种不愉快的东西:蜘蛛,老鼠屋等。

分配完后,计算机缓冲区有点像这样。您真的不能确定其中有什么,只是知道它有多大。

现在,假设您将四个瓶子放在了容器中。您的持有人没有更改大小,但是现在您知道四个空格中的内容。其他两个带有可疑内容的空间仍然存在。

计算机缓冲区是相同的方式。这就是为什么您经常看到 bufferSize 变量来跟踪正在使用多少缓冲区的原因。更好的名称可能是 numberOfBytesUsedInMyBuffer ,但程序员往往会发疯。

答案 5 :(得分:2)

写缓冲区的一部分不会影响缓冲区的未写部分;它会包含事先存在的所有内容(这自然完全取决于您首先获得缓冲区的方式)。

如其他答案所述,静态和全局变量将被初始化为0,但是局部变量将不会被初始化(而是预先包含栈中的所有内容)。这与零开销原则保持一致:在某些情况下,初始化局部变量将是不必要的和不必要的运行时成本,而静态和全局变量将在装入时作为数据段的一部分进行分配。

堆存储的初始化是由内存管理器选择的,但是通常也不会被初始化。

答案 6 :(得分:1)

通常,缓冲区不足是很正常的。分配大于所需数量的缓冲区通常是个好习惯。 (尝试始终计算出准确的缓冲区大小是错误的常见来源,并且通常是浪费时间。)

当缓冲区大于所需的大小时,当缓冲区中包含的数据少于其分配的大小时,跟踪其中有多少数据显然很重要。通常,有两种方法可以执行此操作:(1)使用显式计数并将其保存在单独的变量中,或者(2)使用“前哨”值,例如标记\0的字符C中的字符串。

但是接下来是一个问题,如果不是所有缓冲区都在使用中,未使用的条目包含什么?

一个答案当然是没关系。这就是“未使用”的意思。您关心的是所使用的条目的值,这些值由您的计数或前哨值计算。您不在乎未使用的值。

基本上可以在四种情况下预测缓冲区中未使用条目的初始值:

  1. 当您分配持续时间static的数组(包括字符数组)时,所有未使用的条目都将初始化为0。

  2. 分配数组并为其提供显式初始化程序时,所有未使用的条目都将初始化为0。

  3. 调用calloc时,分配的内存被初始化为全0。

  4. 调用strncpy时,目标字符串用n个字符填充为大小\0

在所有其他情况下,缓冲区的未使用部分是不可预测的,并且通常包含它们上次执行的操作(无论如何)。特别是,您无法预测具有自动持续时间的未初始化数组的内容(即,对于函数而言本地且未用static声明的数组),也无法预测通过{{ 1}}。 (有时候,在这两种情况下,内存往往是在第一次以全零位开始的,但是您绝对不希望依赖于此。)

答案 7 :(得分:1)

这取决于存储类说明符,您的实现及其设置。 一些有趣的例子:  -未初始化的堆栈变量可以设置为0xCCCCCCCC  -未初始化的堆变量可以设置为0xCDCDCDCD  -未初始化的静态或全局变量可以设置为0x00000000  -否则可能是垃圾。 对此做出任何假设都是冒险的。

答案 8 :(得分:1)

已声明的静态持续时间的对象(在函数外部声明的对象,或使用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]拥有相同的值,但不关心该值是什么,则该标准也未提供有关如何编写函数的指南。 >

答案 9 :(得分:1)

我认为正确的答案是,您应该始终跟踪写入了多少个字符。 与诸如读写之类的低级功能一样,也需要给出读写字符的数量。以相同的方式std :: string跟踪其实现中的字符数