为什么printf在未终止的字符串上运行?

7
我想知道printf()函数如何在没有放置终止字符的情况下停止打印字符串。我进行了一个实验,malloc了10个字节的内存,并把恰好10个字符放进去,不知何故,printf函数仍然可以打印这些字符而不会越界,为什么呢?

9
内存中的下一个字节恰好为空? - Kevin Stricker
你是如何“确切地放入10个字符”的?如果你使用了strcpy,那么实际上会把11个字符放进去。 - GrahamS
6个回答

6

很有可能在该字符串后面的字符是NULL,因此printf停止输出,此外,在您动态分配的内存之后不为NULL的字符可能不是可打印字符,因此您在终端上看不到它们。


5

可能是因为你运气不好,所以在你使用malloc分配的字符串中,下一个字节是0字节。

你可以通过以下方式进行确认:

const char* digits = "0123456789";
char* buff = (char*)malloc(10);
memcpy(buff, digits, 10);

printf("%s, %d\n", buff, (int)*(buff + 10));

您的程序需要打印出以下内容:

0123456789 0

那个0是您没有malloc分配但实际上存在的NULL。 请注意,这种行为是未定义的,因此您不能信任这些事情。正如我之前所说,这是因为您不幸!在这种情况下发生的好事情是SIGSEGV。

2
strcpy总是在字符串末尾复制NULL字符。因此,实际上这段代码使用malloc分配了一个10个字符的buff,然后将包括NULL在内的11个字符复制到其中。这个操作会导致在内存中紧挨着buff的任何变量/数据上发生一字节的破坏。 - GrahamS

4
在小程序中,未终止的字符串通常不会引起问题并非只是运气好。
在大多数操作系统/处理器上,malloc将分配舍入到4或8字节的倍数(取决于处理器的内存对齐要求),因此通常(但并非总是)在字符串末尾有一些空闲字节。
通常情况下,当malloc需要更多内存时,操作系统会分配一个或多个虚拟页面(通常为4k)。出于安全原因,如果页面上次被不同进程使用(或自从热重置以来未被使用),则必须擦除页面。
因此,由于有许多零(分配区域和后面的内容)存在,所以在启动或小型短期运行的程序(具有讽刺意味的是,这包括大多数测试程序)中,非终止的字符串很可能不会引起问题,但是当malloc重新使用已释放的块时,它们将会显示出来。
为了防范这类问题,开发和测试构建应该使用像efence这样的东西,并使用EF_FILL选项将malloc的内存设置为非零值。
同样,将堆栈初始化为非零值也是一个有用的想法,因为在大多数具有VM的计算机上,堆栈是从4k页面构建的,在分配给进程之前会被擦除。
请注意,即使使用类似efence的东西,静态变量仍然存在问题-整个区域在程序加载时被擦除为零(并且数据再次对齐),因此如果只写入一次静态字符串变量,则未终止的字符串可能不会被注意到-问题只有在重新使用字符串变量来存储较短的未终止字符串时才会被注意到。
在相关问题上,变量的对齐解释了为什么未分配足够空间以容纳字符串终止符NUL通常不会被检测到。

3
假设您确实使用了malloc分配了10个字符,并且您确实设置了每个字符的值而不是null('\0'),那么在内存中紧随其后的未分配字符是否恰好为null并无保证?
您可能已经使用了一些智能函数调用来将最后一个字符设置为null,即使您传递了足够的信息来使其成为非null,但由于细节太少,我们永远无法知道。

2
字符串最后一个字节之后的随机垃圾是空的。这是幸运的,下次运行程序可能会失败或连续工作100次。欢迎来到指针错误的世界(而且它们很难调试)。

-1

好的,把整个MALLOC的问题放一边,因为对于PRINTF来说,它只是一个字符串,我知道%d、%x、%s等我们用作格式说明符,但事实上printf只是一个"C"函数,可以接受可变数量的参数。

简而言之,printf是一个特殊的函数,将字符串视为传递给它的可变数量CHAR类型参数。

任何\n、\t等参数或%c、%f等都是单个字符,并被视为特殊情况处理。

void myprintf(char * frmt,...)
{

char *p;
int i;
unsigned u;
char *s;
va_list argp;


va_start(argp, fmt);

p=fmt;
for(p=fmt; *p!='\0';p++)
{
if(*p=='%')
{
putchar(*p);continue;
}

p++;

switch(*p)
{
case 'c' : i=va_arg(argp,int);putchar(i);break;
case 'd' : i=va_arg(argp,int);
if(i<0){i=-i;putchar('-');}puts(convert(i,10));break;
case 'o': i=va_arg(argp,unsigned int); puts(convert(i,8));break;
case 's': s=va_arg(argp,char *); puts(s); break;
case 'u': u=va_arg(argp,argp, unsigned int); puts(convert(u,10));break;
case 'x': u=va_arg(argp,argp, unsigned int); puts(convert(u,16));break;
case '%': putchar('%');break;
}
}

va_end(argp);
}

char *convert(unsigned int, int)
{
static char buf[33];
char *ptr;

ptr=&buf[sizeof(buff)-1];
*ptr='\0';
do
{
*--ptr="0123456789abcdef"[num%base];
num/=base;
}while(num!=0);
return(ptr);
} 

希望这可以帮到你,如果不行的话,请告诉我,我很乐意为你提供帮助:)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接