main函数的标准原型应该是
int main(int argc, char *argv[]);
。argc
是命令行参数的个数。而argv
是一个指向指针的指针,为什么不是指针数组呢?因为前面讲过,函数原型中的[]
表示指针而不表示数组,等价于char **argv
。那为什么要写成char *argv[]
而不写成char **argv
呢?这样写给读代码的人提供了有用信息,argv
不是指向单个指针,而是指向一个指针数组的首元素。数组中每个元素都是char *
指针,指向一个命令行参数字符串。
指向非const变量的指针或者非const变量的地址可以传给指向const变量的指针,编译器可以做隐式类型转换,例如
char c = 'a';
const char *pc = &c;
但是,指向const变量的指针或者const变量的地址不可以传给指向非const变量的指针,以免透过后者意外改写了前者所指向的内存单元,例如对下面的代码编译器会报警告:
const char c = 'a';
char *pc = &c;
const int *a;
int const *a;
这两种写法是一样的,a是一个指向const int型的指针,a所指向的内存单元不可改写,所以(*a)++是不允许的,但a可以改写,所以a++是允许的。
int * const a;
a是一个指向int型的const指针,*a是可以改写的,但a不允许改写。
int *a[10] -指针数组与int (*a) [10]-指向数组的指针
我们可以认为[]比*有更高的优先级,如果a先和*结合则表示a是一个指针,如果a先和[]结合则表示a是一个数组。
int *a[10];这个定义可以拆成两句:
typedef int *t;
t a[10];
t代表int *类型,a则是由这种类型的元素组成的数组。int (*a)[10];这个定义也可以拆成两句:
typedef int t[10];
t *a;
t代表由10个int组成的数组类型,a则是指向这种类型的指针。int a[10]; int (*pa)[10] = &a;
a是一个数组,在&a这个表达式中,数组名做左值,取整个数组的首地址赋给指针pa。
注意,&a[0]表示数组a的首元素的首地址,而&a表示数组a的首地址,显然这两个地址的数值相同,但这两个表达式的类型是两种不同的指针类型,前者的类型是int *,而后者的类型是int (*)[10]。*pa就表示pa所指向的数组a,所以取数组的a[0]元素可以用表达式(*pa)[0]。注意到*pa可以写成pa[0],所以(*pa)[0]这个表达式也可以改写成pa[0][0],pa就像一个二维数组的名字,它表示什么含义呢?下面把pa和二维数组放在一起做个分析。
int a[5][10];和int (*pa)[10];之间的关系同样类似于int a[10];和int *pa;之间的关系:a是由一种元素组成的数组,pa则是指向这种元素的指针。所以,如果pa指向a的首元素:
int a[5][10];
int (*pa)[10] = &a[0];
则pa[0]和a[0]取的是同一个元素,唯一比原来复杂的地方在于这个元素是由10个int组成的数组,而不是基本类型。这样,我们可以把pa当成二维数组名来使用,pa[1][2]和a[1][2]取的也是同一个元素,而且pa比a用起来更灵活,数组名不支持赋值、自增等运算,而指针可以支持,pa++使pa跳过二维数组的一行(40个字节),指向a[1]的首地址。
void (*f)(const char *) = say_hello;
void (*f)(const char *) = &say_hello;
say_hello是一种函数类型,而函数类型和数组类型类似,做右值使用时自动转换成函数指针类型,所以可以直接赋给f.
把函数say_hello先取地址再赋给f,就不需要自动类型转换了
exec系统调用执行新程序时会把命令行参数和环境变量表传递给main函数
父进程在创建子进程时会复制一份环境变量给子进程,但此后二者的环境变量互不影响。
fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。
子进程中fork的返回值是0,而父进程中fork的返回值则是子进程的id(从根本上说fork是从内核返回的,内核自有办法让父进程和子进程返回不同的值)
任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信