我目前正在学习一门操作系统课程,我们的学习material 声明了以下代码:

int main() {
   fork();
   printf("hello");
   return 0;
}

可以打印"helloHello"、"hhellollo"、"hheelllloo"等任何组合. 我自己也try 过重现这个行为,但失败了,因为输出总是"helloHello". 这种行为真的有可能吗?还是material 有问题? 如果是,有没有可靠的方法来复制它?

我既try 在fork前添加setbuf(stdout, NULL)来禁用缓冲区,也try 在我们在类中使用的旧版Linux上执行相同的操作,Redhat 7.3(2002年的版本),我认为这可能是一个后来修复的问题. 我还try 将STD标志设置为C99,因为这是我们作业(job)的要求.

我知道以下代码:

int main() {
   fork();
   printf("hel");
   printf("lo");
   return 0;
}

可以以"helhellolo"或"helloHello"结束,但我指的是用一个printf调用打印该行的情况.

我在带有GCC 12.1.1的Linux6.2.7上运行Manjaro,而Redhat VM运行的是带有GCC 3.2的Linux2.4.18,以防万一.

推荐答案

它能做到这一点并不意味着它就能做到.这只是意味着这是可能的.

在我的环境中,Stdio使用4 KiB的缓冲区.这意味着STDIO累积输出,直到它有4 KiB的输出,而不是立即打印.输出以4 KiB区块为单位发送.(stdout有点特殊,因为它还会在换行符和可能的其他条件下刷新.)

#include <stdio.h>

int main( void ) {
   for ( size_t i=0; i<20000; ++i ) {
      printf( "a" );
   }
}
$ strace ./b 2>&1 >/dev/null | grep ^write
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 4096) = 4096
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 4096) = 4096
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 4096) = 4096
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 4096) = 4096
write(1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 3616) = 3616

因此,如果我们准备好缓冲区,使其几乎已满,我们可以从您的代码中获得helhellolo.

#include <stdio.h>
#include <unistd.h>

int main( void ) {
   for ( size_t i=0; i<4096-3; ++i ) {
      printf( "a" );
   }

   fork();
   printf("hello");
}

输出(有时):

...aaaahelaaaa...aaaahellolo

因此,在一个编写超过hello个代码的程序中,printf("hello");的输出完全有可能被截断.

C++相关问答推荐

C sscanf没有捕获第二个参数

想了解 struct 指针和空指针转换

返回一个包含数组的 struct

测量ARM MCU中断延迟的问题

将常量转换为指针会增加.数据大小增加1000字节

Clang:如何强制运行时错误的崩溃/异常由于-fsanitize=undefined

如何使用[BTStack]BLE发送大型(>;2kb)信息包

插座打开MacOS组件

如何在下面的C代码中正确管理内存?

在进程之间重定向输出和输入流的问题

For循环不会迭代所有字符串字符吗?(初学者问题)

搜索使用int代替time_t的用法

将非连续物理内存映射到用户空间

有没有办法减少C语言中线程的堆大小?

生成的头文件不包括用户定义的文件

在C中使用字符串时是否不需要内存分配?

C 和 C++ 标准如何告诉您如何处理它们未涵盖的情况?

尽管将其标记为易失性,但 gcc 是否优化了我的等待代码?

是否可以在多字 C 类型中的任何位置混合存储和类型限定符?

比 * 更快的乘法