一、main函数
- 现在的格式:
int main(int argc, char *argv[])
- 以前的main函数有三个参数,另一个参数就是环境变量
二、进程的终止(两种都要背下来)
2.1 正常终止
(1)从main函数返回
main
函数被称为程序的入口- 许多人写的:
return 0
- 注意:进程的返回值是给父进程看的
- 面试题:
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("hello!\n");
return 0;
}
该进程的父进程为:shell
可以利用:echo $?
查看上一条语句的执行状态
当程序变为下面的样子:
问题:父进程看到子进程的退出状态为多少?
这里应该是:7
因为printf返回值为7(打印了7个字符)
(2)调用exit
- exit( )是库函数
- 例如程序里面写的:
exit(0);
- exit( )可以返回256种状态(-128 到 127):
- 会调用钩子函数:
补充:atexit( )函数:钩子函数
- 钩子函数特别像C++中的析构函数
- 钩子函数的调用是以当初声明函数的逆序方式调用的
- 例子:
#include <stdio.h>
#include <stdlib.h>
static void f1(void) {
puts("f1() is working");
}
static void f2(void) {
puts("f2() is working");
}
static void f3(void) {
puts("f3() is working");
}
int main() {
puts("Begin");
atexit(f1);
atexit(f2);
atexit(f3);
puts("End");
exit(0);
}
运行结果:
(3)调用_exit或_Exit
- _exit( )和_Exit是系统调用函数,exit( )是库函数,这是依赖与前面两个系统调用函数的。
- exit( )和_exit( )的区别:
1)_exit( )是直接结束进程(不执行钩子函数,不进行I/O清理)
2)exit( )要先去终止处理程序,标准I/O清理程序,然后才依赖与_exit( )去结束进程
(4)最后一个线程从其启动例程返回
(5)最后一个线程调用pthread_exit
2.2 异常终止
(1)调用abort
(2)接到一个信号并终止
(3)最后一个线程对其取消请求作出响应
三、命令行参数的分析
几乎shell命令行解析都是这俩函数解决的:
(1)getopt( )函数
-
getopt( )函数:是用来解析命令行选项参数的,但是只能解析短格式:ls -a ,不能解析长格式:ls --all (可以用getopt_long( )解析长格式)
-
参数介绍:
(1)argc:main()函数传递过来的参数的个数
(2)argv:main()函数传递过来的参数的字符串指针数组
(3)optstring:选项字符串,告知 getopt()可以处理哪个选项以及哪个选项需要参数 -
返回值:如果选项成功找到,返回选项字母;如果所有命令行选项都解析完毕,返回 -1;如果遇到选项字符不在 optstring 中,返回字符 ‘?’
-
optstring的格式:
对于格式:char*optstring = “-dab:c::”;
(1)单个字符
a
表示选项 a 没有参数
格式:-a 即可,不加参数(2)单字符加冒号
b:
表示选项b有且必须加参数
格式:-b 100或-b100,但-b=100错(3)单字符加两个冒号
c::
表示选项c可以有,也可以无
格式:-c200,其它格式错误(4)如果在第一个符号是
’-‘
,这表明有有非选项传参,返回值为 1 -
当检查到上面某一个参数被指定时,函数会返回被指定的参数名称(即该字母):
(1)optarg —— 指向当前选项参数(如果有)的指针。
(2)optind —— 再次调用 getopt() 时的下一个 argv指针的索引。
(3)optopt —— 最后一个未知选项。
(4)opterr —— 如果不希望getopt()打印出错信息,则只要将全域变量opterr设为0即可。
具体案例:mydate.c
按照指定格式打印时间,可以通过非选项传参,指定是否将时间打印到指定文件当中去
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
/*
-y: year
-m: month
-d: day
-H: hour
-M: minute
-S: second
*/
#define TIMESTRSIZE 1024
#define FMTSTRSIZE 1024
int main(int argc, char **argv) {
time_t stamp;
struct tm *tm;
char timestr[TIMESTRSIZE];
int c;
char fmtstr[FMTSTRSIZE];
FILE *fp = stdout;
fmtstr[0] = '\0';
stamp = time(NULL);
tm = localtime(&stamp);
while (1) {
c = getopt(argc, argv, "-H:MSy:md");
if (c < 0)
break;
switch (c) {
case 1:
if (fp == stdout) {
fp = fopen(argv[optind - 1], "w");
if (fp == NULL) {
perror("fopen()");
fp = stdout;
}
}
break;
case 'H':
if (strcmp(optarg, "12") == 0)
strncat(fmtstr, "%I(%P) ", FMTSTRSIZE);
else if (strcmp(optarg, "24") == 0)
strncat(fmtstr, "%H ", FMTSTRSIZE);
else
fprintf(stderr, "Invalid argument of -H \n");
break;
case 'M':
strncat(fmtstr, "%M ", FMTSTRSIZE);
break;
case 'S':
strncat(fmtstr, "%S ", FMTSTRSIZE);
break;
case 'y':
if (strcmp(optarg, "2") == 0)
strncat(fmtstr, "%y ", FMTSTRSIZE);
else if (strcmp(optarg, "4") == 0)
strncat(fmtstr, "%Y ", FMTSTRSIZE);
else
fprintf(stderr, "Invalid argument of -y \n");
break;
case 'm':
strncat(fmtstr, "%m ", FMTSTRSIZE);
break;
case 'd':
strncat(fmtstr, "%d ", FMTSTRSIZE);
break;
default:
break;
}
}
strncat(fmtstr, "\n", FMTSTRSIZE);
strftime(timestr, TIMESTRSIZE, fmtstr, tm);
fputs(timestr, fp);
if (fp != stdout)
fclose(fp);
exit(0);
}
讲解:
运行结果:
(2)getopt_long( )函数:分析长格式:ls --all
四、环境变量
- 本质:KEY = VALUE形式
(1)export命名
- 利用:export 命令可以查看当前的环境变量
- 粗浅的理解环境变量:把当前的操作系统比作正在跑的程序,环境变量就是该程序的全局变量。
(2)environ全局变量
例子:myenv.c
打印环境变量
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
int main() {
int i;
for (i = 0; environ[i] != NULL; i++) {
puts(environ[i]);
}
exit(0);
}
运行结果:
(3)getenv( )函数:获取环境变量
例子:getenv.c
获取 PATH 这个环境变量
#include <stdio.h>
#include <stdlib.h>
int main() {
puts(getenv("PATH"));
exit(0);
}
运行结果:
(4)setenv( )函数:改变或者添加环境变量
(5)putenv( )函数:改变或者添加环境变量
五、C程序的存储空间布局
- 假设用的32位环境,虚拟空间是4G
六、库
(1)动态库
(2)静态库
(3)手工装载库
- 类似于pthotoshop软件打开会加载很多库,如果其中其中某一个库加载失败了,程序也不会结束,而是会进行运行,只不过那个库不能使用了
- 这里可以看man手册当中关于:dlopen( )函数的讲解以及后面的例子
七、函数跳转
注意:goto
语句不能跨函数的跳转,此时可以用 setjmp( )
函数和 longjmp( )
函数实现跨函数跳转