“函数接口”那一章里一个不停调 malloc 的习题,发生了一件“怪事”

37 views
Skip to first unread message

徐天淵

unread,
Apr 13, 2014, 2:41:19 AM4/13/14
to learning-l...@googlegroups.com
为这题写的小程序 elcml(习题原文和代码附在后面),它会有一行一行的信息打
印出来。这样运行时没有问题:
./elcml
可如果管道的话,stdout 的内容就“凭空消失”了:
./elcml | tee elcml.log
在屏幕上和文件里都只能看到后面 "Cannot allocate memory" 的信息,stdout
输出的都不见了。

习题原文是:
编写一个小程序不停地调 malloc, 让它耗尽系统内存。观察一下,分配了多少内
存之后才会出现分配失败?内存耗尽之后会怎么样?会不会死机?

代码:

/* elcml.c : Endless Calling molloc() */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
     size_t min_size, size_alloc;

     for (min_size = 1024, size_alloc = 0; 1; min_size *= 2) {
         sleep(1);
         if (malloc(min_size) == NULL)
             perror(NULL);
         else
             size_alloc += min_size;
         printf("%llu bytes allocated.\n", size_alloc);
     }
     return 0;
}

Sean Soong

unread,
Apr 13, 2014, 8:24:33 PM4/13/14
to learning-l...@googlegroups.com


--
您收到此邮件是因为您订阅了Google网上论坛中的“Linux C/C++/系统编程 一站式学习”论坛。
要退订此论坛并停止接收此论坛的电子邮件,请发送电子邮件到learning-linux-c...@googlegroups.com
要查看更多选项,请访问https://groups.google.com/d/optout

ironcrow2013

unread,
Jan 18, 2017, 5:45:45 AM1/18/17
to Linux C/C++/系统编程 一站式学习
转眼三年了啊,现在看看觉得那时实在是很幼稚. 第一原程序写得就很有问题, 先改一下:
/* endlm.c : Endless Calling molloc() */

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


int main(void)
{
     size_t min_size
, size_alloc;

     
for (min_size = 1024, size_alloc = 0; 1; min_size *= 2) {
         sleep
(1);

         
if (malloc(min_size) == NULL) {
             perror
("malloc");
             
exit(1);
         
} else {
             size_alloc
+= min_size;
             printf
("%zu bytes allocated.\n", size_alloc);
         
}
     
}
     
return 0;
}
之所以看不到 stdout 的输出, 是因为输出还在缓冲区. stdio对管道文件默认是fully buffered, 只要将其设置为 line buffered 或 unbuffered 就可以了.在for循环前加一句
     setvbuf(stdout, NULL, _IOLBF, 0);
即可. 


ironcrow2013於 2014年4月13日星期日 UTC+8下午2時41分19秒寫道:

soul11201

unread,
Jan 19, 2017, 2:33:13 AM1/19/17
to learning-l...@googlegroups.com
感觉这个问题很重要,再补充一些我知道的:

1.stdio 里面有描述 对于标准输入、标准输出 un​interactive device是全缓冲,​​interactive devicec 是行缓冲。对于标准错误输出永远不是全缓冲的。
2.异常退出会导致缓冲区里面的数据被丢弃
3.只要底层io依赖libc的都可能会碰到你这个问题,想lua,php等。

其他的一些参考资料:



--
您收到此邮件是因为您订阅了Google网上论坛上的“Linux C/C++/系统编程 一站式学习”群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到learning-linux-c-cpp+unsub...@googlegroups.com
要查看更多选项,请访问https://groups.google.com/d/optout



--

ironcrow2013

unread,
Jan 21, 2017, 11:27:39 AM1/21/17
to Linux C/C++/系统编程 一站式学习
翻个墙真是糟心-_-
总结得很好!
APUE 中有个类似的例子, 在我看的第二版中是在section15.4. 
这个小程序(fig15.18)fork()了之后,父进程每次从stdin中读入一行传给子进程, 后者从这一行中scan出两个整数,算出两数之和并传回给父进程.(编译这个程序需要apue.h和err_sys()的源码,这些书上都有,网站上也提供了电子版源码. 这里的err_sys()就是打印错误信息并退出.)
#include "apue.h"


static void sig_pipe(int); /* our signal handler */


int
main
(void)
{
 int n, fd1[2], fd2[2];
 pid_t pid;
 char line[MAXLINE];


 if (signal(SIGPIPE, sig_pipe) == SIG_ERR)
 err_sys("signal error");


 if (pipe(fd1) < 0 || pipe(fd2) < 0)
 err_sys("pipe error");


 if ((pid = fork()) < 0) {
 err_sys("fork error");
 } else if (pid > 0) { /* parent */
 close(fd1[0]);
 close(fd2[1]);


 while (fgets(line, MAXLINE, stdin) != NULL) {
 n = strlen(line);
 if (write(fd1[1], line, n) != n)
 err_sys("write error to pipe");
 if ((n = read(fd2[0], line, MAXLINE)) < 0)
 err_sys("read error from pipe");
 if (n == 0) {
 err_msg("child closed pipe");
 break;
 }
 line[n] = 0; /* null terminate */
 if (fputs(line, stdout) == EOF)
 err_sys("fputs error");
 }


 if (ferror(stdin))
 err_sys("fgets error on stdin");
 exit(0);
 } else { /* child */
 close(fd1[1]);
 close(fd2[0]);
 if (fd1[0] != STDIN_FILENO) {
 if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
 err_sys("dup2 error to stdin");
 close(fd1[0]);
 }


 if (fd2[1] != STDOUT_FILENO) {
 if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
 err_sys("dup2 error to stdout");
 close(fd2[1]);
 }
 if (execl("./add2", "add2", (char *)0) < 0)
 err_sys("execl error");
 }
 exit(0);
}


static void
sig_pipe
(int signo)
{
 printf("SIGPIPE caught\n");
 exit(1);
}
在这里如果子进程exec的add2程序这么写的话,将形成死锁:(以下为前面给出链接的源码包中的 apue.2e/fig15.19)
#include "apue.h"


int
main
(void)
{
   
int        int1, int2;
   
char    line[MAXLINE];


   
while (fgets(line, MAXLINE, stdin) != NULL) {
       
if (sscanf(line, "%d%d", &int1, &int2) == 2) {
           
if (printf("%d\n", int1 + int2) == EOF)
                err_sys
("printf error");
       
} else {
           
if (printf("invalid args\n") == EOF)
                err_sys
("printf error");
       
}
   
}
   
exit(0);
}


问题就在 `if (printf("%d\n", int1 + int2) == EOF)' 这句, 子进程的输出压在了缓冲区里, 父进程收不到,block在了read()上,子进程于是也接收不到父进程进一步的输出,同样卡住了.
解决办法自然可以和前面一样用setvbuf(), 而作者同时还谈到了如果子进程要exec()的是我们不知道其源码的第三方程序该怎么办呢?作者给出的答案是用虚拟终端,(fig19.10, fig19.11, fig19.12, fig19.13)将slave dup到子进程的stdinouterr, 随后父进程再次fork(), 父进程将master里读到的内容输出到原始的stdout,二儿子把原始的stdin的内容输出到master.这其实可以用线程来实现.

除了这个方法之外,还有没有其他节省资源一点的方法?

zhen y於 2017年1月19日星期四 UTC+8下午3時33分13秒寫道:

soul11201

unread,
Jan 21, 2017, 7:58:35 PM1/21/17
to learning-l...@googlegroups.com

在 2017年1月22日 上午12:27,ironcrow2013 <ironcr...@gmail.com>写道:
作者给出的答案是用虚拟终端,(fig19.10, fig19.11, fig19.12, fig19.13)将slave dup到子进程的stdinouterr, 随后父进程再次fork(), 父进程将master里读到的内容输出到原始的stdout,二儿子把原始的stdin的内容输出到master.这其实可以用线程来实现


​最简单的方式不是消息队列吗😁​
​送你一份翻墙hosts:



Eric S

unread,
Feb 5, 2017, 8:44:39 PM2/5/17
to learning-l...@googlegroups.com
hosts多谢🙏
我还是想不通,消息队列不能通过fd打开,要怎么传给黑箱子程序?(我一开始不知道已经有posix ipc了,前两天研究sysv ipc受了内伤
-_-)

Sent from Mail Master
--
您收到此邮件是因为您订阅了Google网上论坛上的“Linux C/C++/系统编程 一站式学习”群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到learning-linux-c...@googlegroups.com
要查看更多选项,请访问https://groups.google.com/d/optout
Reply all
Reply to author
Forward
0 new messages