注册 登录
编程论坛 C++教室

[原创]深入讲解main()返回值问题

冰的热度 发布于 2007-09-29 17:58, 9477 次点击
*/ --------------------------------------------------------------------------------------
*/ 出自: 编程中国 https://www.bc-cn.net
*/ 作者: 冰的热度
*/ 时间: 2007-9-29 编程论坛首发
*/ 声明: 尊重作者劳动,转载请保留本段文字
*/ --------------------------------------------------------------------------------------



前些天在论坛看到一贴,讨论main()函数的返回值,

有的说必须有返回值,如:return 0;

有的说没有也可以,如:用void修饰main()

但大家的讨论都只留于表面,现在我来试着讲解一下深层含义!

以下观点纯属个人观点,若有不当之处,望高手们不吝赐教

要想真正理解这个问题,首先要了解一下操作系统的外壳

操作系统是由多个功能模块组成的庞大,复杂的软件系统,任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户(操作人员)使用这个程序来操作计算机系统进行工作.
DOS中有一个程序command.com,这个程序在DOS中称为命令解释器,也就是DOS系统的shell.


用户的命令由command执行,首先command把程序加入内存,然后设置CPU的CS:IP指向程序的第一条指令(也就是程序入口,在C++中就是main()函数),从而使程序得以运行.程序结束后,返回到command中,CPU继续运行command.注意在返回的时候要有一个返回值,这样才能安全退出程序(注意我用的是退出一词),之后各寄存器会有恢复动作,如果没有返回值,虽然表面上看程序也正常结束了,但实际上它并没有退出,各寄存器并没有恢复,如CS:IP 还是指向程序尾部,如果得到CS:IP,那么可以让程序继续执行下去,但是所执行的程序已经不是你的代码所在的那段内存空间了,它在执行其它内存中的程序.

我先写这些吧,怎么样,明白了吗?

****************************************************
//第一次补充:

在C++中,如果是void main()的话,理论上说,应该不会正常结束,当然,这完全是我的猜测,

因为在汇编程序中,有int 21h的话,结束后会提示 Program terminated normally,

意思是程序正常结束,然后在用T或P命令,将没有指令执行,

如果没有int 21h这一句,就不会有这个提示,也就是说程序没有正常结束,

此时用T或P命令,CS:IP将指向下一条指令,并执行.

类比一下,C++中也应该是这样,
用int main()
{
......
return 0;
}的话会正常结束,

用void main()的话,虽然也能结束,但似乎会有潜在的问题.

******************************************************

[此贴子已经被作者于2007-10-3 21:11:25编辑过]

41 回复
#2
野比2007-09-29 19:22
有个地方:程序结束后,返回到command中
这里没有讲好,是这样的,程序结束后,CS:IP指向command.com进入用户程序前的下一条语句(最后一条语句是“然后设置CPU的CS:IP指向程序的第一条指令”)
此时,command.com从stack里弹出(pop)2或4字节的内容,这些内容就是用户程序的返回值。
没有返回值就是0,所以编写正常的main就算是void型的也可以照常退回dos。

再好好修改扩展一下,写好点加精。
#3
冰的热度2007-09-29 20:39
有更新,请看原贴下的补充!
#4
栖柏2007-09-29 20:58

因为在汇编程序中,有int 21h的话,结束后会提示 Program terminated normally,

意思是程序正常结束,然后在用T或P命令,将没有指令执行,


在dos下用会出现的非正常终止,如果是直接在windows下运行.exe文件就不会显示
不过我有个问题,你的汇编的程序中有ret么?
如二楼,保存现场
不知道正常退出和非正常退出
请参考return和exit

#5
china25qd2007-09-29 21:24
不是所有的main()都能返回"0"的
我比较喜欢main(数据类型 e)
...
return e;
#6
栖柏2007-09-29 21:46
楼上能具体举个例子么?我还不知道,想学习
#7
野比2007-09-30 13:56

MOV AH,4CH
INT 21H



RET

是等价的,只是INT不会PUSH, RET和RETF会

#8
miyayaya2007-09-30 16:53
嗯嗯,学习了

LZ跟野比两位辛苦了
#9
冰的热度2007-09-30 17:49
回复:(miyayaya)嗯嗯,学习了LZ跟野比两位辛苦了...
请问 LZ 是谁呀?
#10
leeco2007-09-30 23:14
说了=没说
#11
felicia2007-10-01 00:02
以下是引用leeco在2007-9-30 23:14:59的发言:
说了=没说

agree

#12
yuyunliuhen2007-10-01 08:58
以下是引用leeco在2007-9-30 23:14:59的发言:
说了=没说

盖公章。名曰“同意”

#13
栖柏2007-10-01 09:16

,呵呵,有意思

#14
mingreign2007-10-01 11:31
回复:(冰的热度)[原创]深入讲解main()返回值问题
    你说了,如果没有返回值,程序结束后可以继续执行其他的程序,也就是再这个程序后面的程序.然而,大家都经常碰到按任意键,可以继续执行着个程序呀.
#15
秀才2007-10-01 17:41


用户的命令由command执行,首先command把程序加入内存,然后设置CPU的CS:IP指向程序的第一条指令(也就是程序入口,在C++中就是main()函数),从而使程序得以运行.程序结束后,返回到command中,CPU继续运行command.注意在返回的时候要有一个返回值,这样才能安全退出程序(注意我用的是退出一词),之后各寄存器会有恢复动作,如果没有返回值,虽然表面上看程序也正常结束了,但实际上它并没有退出,各寄存器并没有恢复,如CS:IP 还是指向程序尾部,如果得到CS:IP,那么可以让程序继续执行下去,但是所执行的程序已经不是你的代码所在的那段内存空间了,它在执行其它内存中的程序.


你听谁说应用程序加载是直接进入main()函数的?你以为编译器那么笨你不写return函数,函数执行完后就不会退栈返回?你写一个void function(void)函数然后让main调用,该函数不写return你看能不能返回到main中。建议你去买本谭老的c语言入门教材从头学起。

不知你有没有念过本科,学没学一点操作系统原理的课,知不知道什么是进程。你喜欢谈shell我就跟你讨论下shell,下面是个简单的shell的实现:

程序代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAXLINE 256


int main(void)
{
    char buf[MAXLINE];
    pid_t pid;
    int status;


    printf(\"%%\");   //pintf prompt
    while(fgets(buf,MAXLINE,stdin)!=NULL){
       if(buf[strlen(buf)-1]=='\n')
        buf[strlen(buf)-1]==0;    //replace newline with null


       if((pid=fork())<0){
        printf(\"fork error\n\");
        exit(1);
       }
       else if(pid==0){       //child process
        execlp(buf,buf,(char*)0);
        printf(\"couldn't execute: %s\",buf);   
        exit(127);
       }


       if((pid=waitpid(pid,&status,0))<0){       //child process
        printf(\"waitpid error\");
        exit(1);
       }
   
       printf(\"%%\");
    }
}


fork函数创建子进程,子进程调用exec函数簇的一个(这里是execlp)执行另一个程序,执行到exec时该进程用新的程序替换旧的程序,新的程序再从main开始执行,exec的作用就是用新的程序替换当前进程的正文,数据,堆和栈。

如果你想在windows系统中实现,做一个command.com或cmd.exe,只需要把fork换成CreateProcess,execlp对应的是ShellExecute,不过这里还有一些区别,这里的CreateProcess功能强一点不用再用ShellExecute,如果不另开进程也可以只用ShellExecute(不过这样就不是shell了)。一个程序在shell中可以选择后台运行,这样在打开子进程后可以继续解释命令不用等待,你的什么不能返回是扯淡。不要拿些个指令指针CS:IP来混弄人,或许你学了点8086实模式16位汇编,win32asm不知道你有没有学过。


在C++中,如果是void main()的话,理论上说,应该不会正常结束,当然,这完全是我的猜测,

你的帖子中全是这种“理论上”,“可能”,“猜测”的词,不知道你不会去查一下资料搞清楚?

俺刚来,也没有什么NB的技术,只是见不得别人糊弄初学者。

[此贴子已经被作者于2007-10-1 17:44:23编辑过]

#16
冰的热度2007-10-01 18:28
回复:(秀才)[quote]用户的命令由command执行,首先c...
大家鼓掌......

感谢你给我补充了点"营养元素"

学到东西喽,耶......
#17
秀才2007-10-01 19:11
#18
栖柏2007-10-02 22:35
楼上有个问题说的不太清楚
关于void main();的
请问void 会转为int 么?
#19
编程高手2007-10-03 02:10
LZ分明是不懂装懂嘛,错漏百出,还说那么多好像和似乎之类的词,明显就是滥竽充数
还有楼上,这么简单的问题怎么问得出来啊,如果会自动转那还写int main干嘛?动动脑子都想得到啊
#20
cince2007-10-03 12:15
楼上的分明是吃饱撑着没事干.

看了秀才的回复..虽然语言有点那个...但讲解的很精彩...可是...在你的回复中却不见解说"进

程"是指什么?以及他的理论..能否纯理论的讲讲?(本人是初学者,忘请见谅).

对于楼主...他所写的只是自己的看法...个人的看法也许是错的..但是把他拿出来共享,却不失一个程

序员的风范..

对于秀才呢..反驳有理也有据..而且让我学到很多(鼓掌)..我也得学学其中的技术(不学他的说话方式.

或许我以前有那样子)

#21
栖柏2007-10-03 12:18
以下是引用编程高手在2007-10-3 2:10:00的发言:
LZ分明是不懂装懂嘛,错漏百出,还说那么多好像和似乎之类的词,明显就是滥竽充数
还有楼上,这么简单的问题怎么问得出来啊,如果会自动转那还写int main干嘛?动动脑子都想得到啊

能有能的理由,不能有不能的理由,理由是我想知道在这中间操作系统做了什么?编译器做了什么,难道你知道...就请说说吧

#22
冰的热度2007-10-03 21:19
请读者注意我的用词,特别是红字,

如果读者想得到一个权威的完美的答案,肯定会令大家失望,我还没到那个境界,

另外,我是单纯从语言的角度分析的,没有结合操作系统来讲

#23
冰的热度2007-10-03 21:25
回复:(编程高手)LZ分明是不懂装懂嘛,错漏百出,还...
1.有句话说的好呀,"不怕你想错,就怕不去想"

2.请告诉我,到底 LZ 是谁呀! 或是什么意思呀?
#24
缘吇弹2007-10-03 21:26
以下是引用秀才在2007-10-1 17:41:52的发言:


用户的命令由command执行,首先command把程序加入内存,然后设置CPU的CS:IP指向程序的第一条指令(也就是程序入口,在C++中就是main()函数),从而使程序得以运行.程序结束后,返回到command中,CPU继续运行command.注意在返回的时候要有一个返回值,这样才能安全退出程序(注意我用的是退出一词),之后各寄存器会有恢复动作,如果没有返回值,虽然表面上看程序也正常结束了,但实际上它并没有退出,各寄存器并没有恢复,如CS:IP 还是指向程序尾部,如果得到CS:IP,那么可以让程序继续执行下去,但是所执行的程序已经不是你的代码所在的那段内存空间了,它在执行其它内存中的程序.


你听谁说应用程序加载是直接进入main()函数的?你以为编译器那么笨你不写return函数,函数执行完后就不会退栈返回?你写一个void function(void)函数然后让main调用,该函数不写return你看能不能返回到main中。建议你去买本谭老的c语言入门教材从头学起。

不知你有没有念过本科,学没学一点操作系统原理的课,知不知道什么是进程。你喜欢谈shell我就跟你讨论下shell,下面是个简单的shell的实现:

程序代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAXLINE 256


int main(void)
{
    char buf[MAXLINE];
    pid_t pid;
    int status;


    printf(\"%%\");   //pintf prompt
    while(fgets(buf,MAXLINE,stdin)!=NULL){
       if(buf[strlen(buf)-1]=='\n')
        buf[strlen(buf)-1]==0;    //replace newline with null


       if((pid=fork())<0){
        printf(\"fork error\n\");
        exit(1);
       }
       else if(pid==0){       //child process
        execlp(buf,buf,(char*)0);
        printf(\"couldn't execute: %s\",buf);   
        exit(127);
       }


       if((pid=waitpid(pid,&status,0))<0){       //child process
        printf(\"waitpid error\");
        exit(1);
       }
   
       printf(\"%%\");
    }
}


fork函数创建子进程,子进程调用exec函数簇的一个(这里是execlp)执行另一个程序,执行到exec时该进程用新的程序替换旧的程序,新的程序再从main开始执行,exec的作用就是用新的程序替换当前进程的正文,数据,堆和栈。

如果你想在windows系统中实现,做一个command.com或cmd.exe,只需要把fork换成CreateProcess,execlp对应的是ShellExecute,不过这里还有一些区别,这里的CreateProcess功能强一点不用再用ShellExecute,如果不另开进程也可以只用ShellExecute(不过这样就不是shell了)。一个程序在shell中可以选择后台运行,这样在打开子进程后可以继续解释命令不用等待,你的什么不能返回是扯淡。不要拿些个指令指针CS:IP来混弄人,或许你学了点8086实模式16位汇编,win32asm不知道你有没有学过。


在C++中,如果是void main()的话,理论上说,应该不会正常结束,当然,这完全是我的猜测,

你的帖子中全是这种“理论上”,“可能”,“猜测”的词,不知道你不会去查一下资料搞清楚?

俺刚来,也没有什么NB的技术,只是见不得别人糊弄初学者。


你这贴也可谓是精华了.

#25
缘吇弹2007-10-03 21:27
以下是引用冰的热度在2007-10-3 21:25:57的发言:
2.请告诉我,到底 LZ 是谁呀! 或是什么意思呀?

在本贴指的是阁下.

#26
hero45872007-10-03 22:07
我是初学者   看了帖子学了不少东西   但是好象 lz 是 汉语 楼主  的  拼音缩写   一半指发帖子的 作者
#27
冰的热度2007-10-03 22:17
哦,对对对,有道理.谢了.
#28
aipb20072007-10-04 12:56
你怎么总这样啊?
#29
郭芙蓉2007-10-04 20:44
秀才过来看你写的什么狗屁东西:

if((pid=waitpid(pid,&status,0))<0){       //parent process
        printf(\"waitpid error\");
        exit(1);
       }


你知道我郭女侠最鄙视糊弄初学者的,这种人卑鄙下流无耻,只为换取初学者心目中的高手形象,我见一次打两次。
秀才你不是这种人吧?
#30
Knocker2007-10-04 23:20
秀才,偶不与你抢了,原来是个母老虎
#31
china25qd2007-10-04 23:55
以下是引用野比在2007-9-29 19:22:23的发言:
有个地方:程序结束后,返回到command中
这里没有讲好,是这样的,程序结束后,CS:IP指向command.com进入用户程序前的下一条语句(最后一条语句是“然后设置CPU的CS:IP指向程序的第一条指令”)
此时,command.com从stack里弹出(pop)2或4字节的内容,这些内容就是用户程序的返回值。
没有返回值就是0,所以编写正常的main就算是void型的也可以照常退回dos。

再好好修改扩展一下,写好点加精。

现在就能加精了

#32
秀才2007-10-06 13:33
回复:(郭芙蓉)秀才过来看你写的什么狗屁东西:[cod...

芙妹,我那是无心之错。子曾经曰过:有心为善虽善不赏,无心之过虽恶不罚。

我等下再回个帖子来弥补过错。

PS:楼上的那位你不知道CS:IP是楼主糊弄人的吗?现在PC普及在32位上,指令指针已经是EIP了,windows也用平坦内存模式,段寄存器的概念已经模糊了。

#33
秀才2007-10-06 13:48



子曾经曰过:源码在前,了无秘密.
我15楼已经给出了一个简单的shell实现,不过只是分析了执行程序时进程的活动,main函数的调用和返回没有详细说,现在尽我所知尽可能的说详细些,以UNIX系统为例:

当系统执行c程序时是通过一个exec调用,在这个调用中进入main之前还有个特殊的启动routine,该routine是进程中程序的入口地址,工作是从OS内核中取得命令行参数和环境变量,打开stdin,sdtout,stderr三个文件流,为进入main做准备。之后的工作就是调用main了,main返回之后该进程立即调用exit函数,将main的返回值传递到OS内核。由于该routine通常用汇编写,这里给出等价的c代码:exit(main(argc,argv));exit函数会执行标准IO库的清理关闭操作:为所有打开流调用fclose函数,让所有缓冲的数据都被冲洗,写到文件中。

exit使用一个整形参数(shell提供从内核获取该值的功能),若main执行一个无返回值的return语句或main没有声明返回值为整形,该进程的终止状态是未定义的,但若main的返回值声明为整形但执行到最后一句时没有显式的return或exit()一个整数,那么该进程的终止状态是0。这种处理是C99规定的,C99标准之前没有显式返回的进程的终止状态也是未定义的。见下例:


#include<stdio.h>
main()
{
printf(\"hello.world\n\");
}

编译该程序后运行可以看到终止码是随机的,不同的系统上编译该程序会有不同的结果,取决于main返回时栈和寄存器的内容:

$ cc hello.c
$ ./a.out
hello,world
$ echo $?
13

如果启用C99编译器扩展就可以看到终止码改变了:

$ cc -std=c99 hello.c
hello.c:c4:warning:return type defaults to 'int'
$ ./a.out
hello,world
$ echo $?
0

上面几段差不多说清了main的一生一世,进入到退出,不过还是比较杂乱,看的让人有点头晕,那个简易的shell模型启动应用程序的过程中执行流程在用户态和内核态之间来回穿梭,下面给个图说明:

用户态 【shell】----- --【新的shell进程】-- --【执行main的进程】--
↓fork ↑ ↓exec ↑ ↓return或exit调用
------------------------------------------------------------------------------------------------------------------
↓ ↑ ↓ ↑ ↓
内核态 内核fork服务------- 内核exec服务----- 获取终止码撤销进程服务


OVER,以我目前的功力只能分析到这一层了,我师弟已于前日退出论坛,我在这里也没意思,回去闭关修炼去也。
PS:那些个不学无术的最好找个CS的本科专业认真念四年,不才在CS&T专业混了四年才把一只脚伸进CS的大门。还有那个叫栖柏的小P孩不要再灌水了,回去老实念书去。

================================================================================================================
Bibliography
Addison Wesley,Advanced Programming in the UNIX Environment: Second Edition
By W. Richard Stevens, Stephen A. Rago
Person Education,Modern Operating Systems:Second Edition
By Andrew S.Tanenbaum


#34
冰的热度2007-10-06 16:25
再次鼓掌......

谢谢又给我补充了些"营养元素"

ありがと



[此贴子已经被作者于2007-10-6 16:39:47编辑过]

#35
栖柏2007-10-06 19:00
这帖精彩!
#36
aipb20072007-10-06 19:11
回复:(秀才) 子曾经曰过:源码在前,了无秘密. ...
精华
#37
冰的热度2007-10-08 21:34
我又有了新的想法,但还没得到答案,所以还不想补充到原贴中,

新的想法如下:

现在可以基本肯定一点,main()函数最后结束之后,要返回操作系统,

那么CPU会对相关寄存器做些恢复操作,

但是到底做了哪些工作,目前我实在不知道,

我觉的要清楚的解释其来龙去脉可能要涉及到内核级的知识,

所以我希望内核级的超级大师点拨我一下.

不懂的就不要乱发贴 瞎 嚷 嚷 了!

[此贴子已经被作者于2007-10-8 21:36:29编辑过]

#38
sdz2007-10-09 20:01
关注..
#39
mbstorm2008-11-01 13:30
怎么看不到 作者被禁止或删除 内容自动屏蔽
#40
mbstorm2008-11-03 21:46
怎么没有啊
#41
emo2692008-11-28 12:23
支持~~~~~~~
#42
hitcolder2008-11-28 16:09
看不到啊
1