1、通过静态的定义char *p = "abc";系统会默认的给字符串"abc"后面加上'\0'。此时char *p = "abc";在内存中的存储形式为"abc\0"。
并且经过验证无论字符串是什么形式,哪怕是char *p = "def\0";编译器也会自动给"def\0"字符串后面加上字符'\0'。此时char *p="def\0";在内存中的存储形式为"def\0\0"。
而且,不光是静态定义。在下面的情况下系统也会默认使用上面的规则给字符串后面加上字符'\0':
......
printf("%d\n",strcmp("abc","abc\0dkjglsdjg\0")); //此时判断结果为相等(输出0),原因是strcmp只比较两个参数的,从左向右第一个'\0'之前的所有字符是否相等。
//经过编译器的处理,左参数实际在内存中为"abc\0",右参数在内存中存储形式实际为"abc\0dkjglsdjg\0\0",所以比较结果为“相等”
printf("%d\n",strlen("xyz")); //在内存中的存储形式是"xyz\0"
2、在定义结构体时不能对结构体中的成员变量赋值。必须等到malloc给结构体实例化并分配了相应的空间后才能对机构体中的成员变量赋值。比如:
//错误代码:
#include <stdio.h>
void main(void)
{
struct bo
{
int a = 10
};
}
//正确代码:
#include <stdio.h>
void main(void)
{
struct bo
{
int a;
};
}
结构体除了用malloc手动申请内存外,还可以让系统静态自动分配内存:
#include <stdio.h>
struct boy
{
char name[10];
}; //注意,这里必须要有;号,否则会报错类似错误:error: incompatible types when returning type ‘int’ but ‘struct st1’ was expected
int main(void)
{
struct boy xiaoming;
xiaoming.name[0] = 'x';
xiaoming.name[1] = 'i';
xiaoming.name[2] = '\0';
printf("\%sn",xiaoming.name);
return 0;
}
3、一点哲学:不要专门为了去用宏,而用宏。
4、规律:
对于长度相等的数A和数B,则将A变为B的方法是:
(1)A'= A | B //先将A与B进行“或”运算,再将运算结果付给A'
(2)A''= A' & B //再将(1)中得到A'与B进行“运算”得到A''
则最终得到的A'' == B成立。
注意:该规律用于控制对A的操作次数。例如:
while(1) //本循环体中的if语句只执行一次
{
if(B != A & B ) //假设A与B在第一次&操作时所得到的值永远不为B
{
A = A | B; //关闭下次if语句入口,本if体以后不在执行
//进行相应的操作
........
}
}
4、程序一旦强行终止(ctrl+c),则该程序所占用的所有堆 和 栈资源都被释放。堆 和 栈是属于程序的。
5、printf("nihao+ma\n");等价于printf("nihao+""ma""\n");等价于printf("nihao+""""ma""""\n");。。。以此类推。
打印宏的方法:
#define VERSION "10.0.4"
int main()
{
printf("VER NUMBER: "VERSION"\n");
return 0;
}
在printf中""具有特殊含义,但是两对""则外面的""就会和里面的“”相抵消,实现的效果就是没有""。
6、C语言中,在进行逻辑判断时,0代表假,除0之外的所有数值(包括负值)都代表真。NULL也代表假。
7、linux中C语言标准库中对NULL的定义如下(定义在/usr/include/linux/stddef.h中):
#ifndef _LINUX_STDDEF_H
#define _LINUX_STDDEF_H
#undef NULL
#if defined (__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
也就是说在C和C++中 NULL 的定义不同。在C中NULL 是(void *)0,而在C++中NULL是0。
8、经实验发现:设p是指向动态申请的一块内存的指针,free(p);后依然可以对原先p所指向的内存进行写操作,而且操作系统不报segmentation fault错误。但是设c是一个指针,给c赋一个堆地址,然后对c所指向的内存区域进行写操作,此时操作系统会报segmentation fault错误。
9、malloc申请得到内存后并不对内存内容用0x00进行初始化。而calloc申请得到内存后会对得到的内存内容用0x00进行初始化。
10、free(p);后,p所指向的内存的数据会发生变化,但不是全部初始化化为0x00。具体过程需要查资料。
11、对于下面的程序用gcc4.4.3编译报错。
#include <stdio.h>
int a = 10;
int b = a;
void main(void)
{
int c = a;
return;
}
错误原因是对于全局/静态区的变量是在程序运行前就初始化的,并且只初始化一次,在程序运行的过程中是不会对全局和静态区的变量初始化的。并且程序运行前初始换的全局和静态变量在同一文件中是按照程序中的顺序初始化的,不同文件间的全局和静态变量初始化的顺序是不确定的。所以为了消除文件A全局和静态变量初始化对文件B中全局和静态变量的初始化的依赖(这种依赖造成了哪个文件必须先初始化,哪个文件必须后初始化,而这中先后关系和程序在运行前不同文件间的全局和静态变量初始化的不确定,产生了矛盾),所以,为了消除这种依赖而带来的麻烦,C语言规定:全局和静态变量(静态变量包括全局和局部)必须用常量初始化,并且如果用户不手动明确初始化,则系统用默认常量初始化。
注意:但是上面的情况只是在gcc中报错,在g++中可以编译通过。
12、c语言的所有标准都规定了int main,而void main是微软自创的。所以,在写main函数是定义为int main是兼容性最好的,因为所有的编译器都支持,并且程序的安全性也更高。
13、const关键字在C和C++中都存在。只不过const在C中是用来定义read-only变量的,注意在C中被const修饰后的量还是变量,只不过成了只读变量了。
而在C++中,被const修饰后的量就是常量了,相当于宏,但是又比宏安全。因为在C++中,宏是没有类型检查的,而const修饰的常量有类型检查。
所以:我们经常听见说C语言中的“指针常量”和“常量指针”这种叫法本身就是错的。标准的说应该是“只读,指针变量”和“指向只读变量的,指针变量”。
15、对于下面的函数:
int fun(void)
{
static a = 10;
return a--;
}
这个函数返回的是a,并且执行这个函数后a的值会减一。(提示:这是一种不好的编程风格,容易让人忽略局部“静态”变量a在最后还要减一)
16、linux shell中,任何一个文件描述符a都可以同时用来进行输入输出操作,只要指定了文件描述符a的输入文件X和输出文件Y,则对文件描述符a进行输入操作时,会自动从X文件输入数据。对文件描述符a进行输出操作时,会自动将数据输出到文件Y。也就是说一个文件描述符最多对应两个文件,但是一个文件可以对应多个文件描述符。
17、linux中,在子进程代码判断结构之前,代码中创建的所有文件描述符,在最终的父子进程中值大小一样。尽管此时父子进程拥有相同大小的文件描述符,但是父子进程中这些相同大小的文件描述符还是有区别的,这些文件描述符是和进程密切相关的。例如:父子进程中都有文件/root/book的文件描述符,并且都是5。则,在子进程中close(5)后,父进程依然可以正常通过值为5的文件描述符访问/root/book文件。在linux的管道中,这点尤其明显。如果在父进程创建子进程前,父进程已经创建了管道。则,管道文件只有一个,并且该管道文件共享于父子进程间。此时,该管道文件上有两对读写端口,一对是父进程的,一对是子进程的。虽然父子进程的管道读写端口文件描述符的数值表面上都一样,但是实际上是和进程相关的。要想使对该共享于父子进程间的管道文件读失败,就要关闭所有连接于该管道文件的写端口。也就是父子进程都要close(pipe_fd[1])。只有这样,以后不管是父进程还是子进程对该管道的读操作都会失败并且返回0值。(具体可以参考《linux 程序设计4》中13.5.1节)。下面是一个简单的验证代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc,char **argv)
{
int pipe_fd[2] = {0};
int f = 0;
int status = 0;
char *p = "abc";
char buf[4] = {0};
int fd = 0;
if(0 > pipe(pipe_fd))
{
printf("pipe failed!\n");
exit(-1);
}
f = fork();
fd = open("/root/book",O_RDWR);
if(0 > f)
{
printf("fork error\n");
}
else if(0 == f)
{
printf("child:pipe_fd[0] = %d\n",pipe_fd[0]);
printf("child:pipe_fd[1] = %d\n",pipe_fd[1]);
close(pipe_fd[0]);
close(pipe_fd[1]);
close(fd);
printf("child:fd = %d\n",fd);
fd = 0;
printf("child:set fd = 0\n");
exit(0);
}
else
{
wait(&status);
printf("father wait ok\n");
printf("father:pipe_fd[0] = %d\n",pipe_fd[0]);
printf("father:pipe_fd[1] = %d\n",pipe_fd[1]);
printf("father:fd = %d\n",fd);
printf("write fd: %d\n",fd);
if(0 > write(fd,p,3))
{
printf("write error!\n");
exit(-1);
}
printf("read fd: %d\n",fd);
lseek(fd,0,SEEK_SET);
read(fd,buf,3);
printf("pipe:%s\n",buf);
}
exit(0);
}
运行结果:
root@bt:~# ./a.out
child:pipe_fd[0] = 3
child:pipe_fd[1] = 4
child:fd = 5
child:set fd = 0
father wait ok
father:pipe_fd[0] = 3
father:pipe_fd[1] = 4
father:fd = 5
write fd: 5
read fd: 5
pipe:abc
18.经过验证父进程A在fork出子进程B后,子进程B内部调用execl调用替换子进程B的映像,此时,execl调用产生的新的进程C依然和原先的父进程A保持父子关系,并且C进程的享有原先子进程B的一切资源,比如C进程的进程号和B进程一样,C进程可以直接使用B进程已经打开的文件描述符(需要在execl调用的时候将B进程有效的文件描述符通过参数传递给execl才行)。
19. 对于C语言中的struct结构体,有下面这种特性:
struct d
{
};
struct a
{
struct b *b;
struct c *c;
struct d dd;
};
struct c
{
};
int main(void)
{
return 0;
}
对于上面这样的代码,使用GCC是可以编译通过的而且没有报错。说明一个问题:就是GCC在C语言中对于struct结构体中的指针P,如果该指针P指向的是一个struct类型,那么,编译不会对该指针P所指向的那个struct类型进行检查。也就是说即便P所指向的那个类型没有提前定义,编译器也不会报错。除非,在后续代码中使用指针P,此时编译才会发现并报错。并且,struct中指向结构体类型的指针P不要求它所指向的那个类型必须提前定义,也就是说,对于下面这种情况:
#include <stdio.h>
struct a
{
struct b * p;
};
struct b
{
int age;
};
int main(void)
{
struct a boy = {0};
(boy.p)->age = 10;
return 0;
}
即使在struct a中提前使用了struct b类型,而后定义了struct b类型,编译器依然成功编译,不会报错。
对于C语言中struct结构体的这个原理,使用最多的就是linux中/usr/include/sys/queue.h文件中的宏函数,具体可以查看该queue.h头文件。
20.在linux中文件描述符是相对进程而言的。对于文件TEXT,它可以被一个A进程打开多次,从而返回文件描述符3,4,……,也可以同时被B进程打开多次,从而返回文件描述符3,4,……。对于A和B进程而言,返回的3,4,……是对进程A或进程B自身而言的。这些文件描述符在系统中是独一无二的。也就是说文件描述符是和进程相关的。脱离了进程也就没有了意义。