Expert C Programming(C专家编程) 读书笔记
目录
- 几个比较奇葩的指针赋值
- int (* fun())()
- int (* foo())[]
- int (*foo[])()
- const 关键词的意义是什么?
- char const (*next )() 怎么读?
- char (c[10])(int **p) 怎么读?
- typedef 不应该怎么用?
- definition 和 declaration 有什么不同?
- 数组和指针之间的区别是什么?
- ABI 是什么?动态链接的目的是什么?
- 静态库和动态库怎么创建?
- 编译器是怎么赋值的?
- 如何生成链接动态库的文件?
- 哪些关键字是最好别定义成变量名的?
- 关于链接的一些小事
- magic numbers 是什么?
- segment 是什么概念?在 x86 和 UNIX 下有什么区别?
- 如何查看 segment 的大小?
- 存储介质的访问速度排序
- swap out 是什么? swap area 又是什么?
- 堆的实现原理是什么?
- 使用 malloc 函数的注意事项?内存泄露是怎么回事?
- 如何预防内存泄露?
- 如何查看内存泄露?
- 内存的一些知识
- 指针和数组的规则总结
- 数组在内存里面是怎么分布的?
- 数组的真实形态
- c 定义动态数组的方法
- system call 和 library call 有什么不同?
- file descriptor 和 file pointer 区别
几个比较奇葩的指针赋值
int (* fun())()
表示 fun() 这个函数,返回一个指针,这个指针指向一个返回 int 类型的函数。
int (* foo())[]
表示 foo() 这个函数,返回一个指针,这个指针指向 int 类型的数组
int (*foo[])()
foo[] 这个数组里面都是指针,这些指针指向一个返回 int 类型的函数
const 关键词的意义是什么?
他意味着,用他声明的变量只读,因为他就表示 constant ,就是常量。
char * const (next )() 怎么读?
next 这个指针,指向一个函数,这个函数返回一个只读的指针,这个指针指向 char 类型。
char (c[10])(int **p) 怎么读?
下面这句话代表了,C是一个含有十个元素的数组,其中每个元素都是一个指针,这些指针分别指向一个形参是 int**p 的函数,这个函数返回一个指针,这个指针指向 char
typedef 不应该怎么用?
千万千万不要将 typedef 这么用,这样子用意义不明确。
typedef int *ptr, (*fun)(), arr[5];
/* ptr is the type “pointer to int”
* fun is the type “pointer to a function returning int”
* arr is the type “array of 5 ints”
*/
以及
unsigned const long typedef int volatile *kumquat;
definition 和 declaration 有什么不同?
Definition 只能出现一次。
declaration 能出现很多次。
数组和指针之间的区别是什么?
pointer | arrays |
---|---|
保存数据的地址 | 保存数据 |
数据是被间接访问的 | 数据是被直接访问的 |
内存空间用 malloc() 分配和 free() 解除 | 内存空间隐式分配和解除 |
ABI 是什么?动态链接的目的是什么?
ABI 是我们使用动态链接的目的。
全称叫做 Application binary interface
ABI 保证了一些库是肯定会出现在编译环境上的,比如说libc (C runtimes), libsys (other system runtimes), libX (X windowing), libnsl (networking services)
其他的库可以静态链接,也可以动态链接。
静态库和动态库怎么创建?
任何人都可以创建静态库。
你将代码进行编译(不要编译 main 函数),然后使用正确的文件编译出 .o 文件,名字前缀为 ar 的是静态库,为 ld 的是动态库。
编译器是怎么赋值的?
被分配在等号左边的符号叫做 l-value 被分配在右边的符号叫做 r-value
编译器分配地址给每个左边的符号,分配数值给右边的符号,左边符号的地址在编译时期就已经是知道的了,与之相反,右边符号的数值要等到运行的时候才知道。
如果右边符号的数值在编译时期就要知道,那么编译器会通过左边符号的地址来进行寻址,找到具体的数值之后,会将他放到寄存器里面。
如何生成链接动态库的文件?
动态库都靠一个链接工具,名为 ld 命令。
一般动态库文件的后缀都是 .so 意思是 share object
哪些关键字是最好别定义成变量名的?
关键词不是不可以被定义成变量名,只是因为这样子做会让你的程序很难理解,并且调试的时候会很不顺畅。
下面这些是 C 里面的保留字
关于链接的一些小事
-
动态链接库,一般叫做 .so 而静态链接库,一般叫做 .a
-
你告诉编译器链接某个库是使用缩写的,比如 libthread.so 可以使用 -lthread
magic numbers 是什么?
可执行文件在 Unix 里面也是有标签那样的东西的,我们称这些东西为 magic numbers
程序可以通过 magic numbers 来推断出某个比特的集结体是什么东西,比如说可以通过下面的 magic number 来推断出他代表的是 superblock
#define FS_MAGIC 0x011954
segment 是什么概念?在 x86 和 UNIX 下有什么区别?
在 Unix 下面,segment 代表了一堆相关联的 section 联结在一起的东西。
在 x86 下面,内存模型不是全部一个整体的,而是被分成很多个 segment ,每一个 segment 大小是 64 Kbyte
如何查看 segment 的大小?
直接对某个文件运行 size 就行了。
比如 size a.out
存储介质的访问速度排序
swap out 是什么? swap area 又是什么?
堆的实现原理是什么?
堆的结尾用一个叫做 break 的指针指向着,当 heap manager 觉得需要更多的内存的时候,就将这个指针向后移动,通过使用 brk() 和 sbrk(),这两个指令你是无法通过显示指定的。
但是如果你使用 malloc 分配了足够的内存,brk() 会自动为你调用的。
使用 malloc 函数的注意事项?内存泄露是怎么回事?
C 没有垃圾回收,所以你必须很小心的使用 malloc() 和 free()
因为 malloc 函数是分配 heap 的内存给你,如果你没有及时清除,有可能会造成内存泄露。
如何预防内存泄露?
一个简单的预防内存泄露的方法,就是使用 alloca() 函数,这个函数会从 stack 里分配内存,而 stack 里的内存,都是用完就回收的。
但是在 stack 里面的东西,就最好不要放长期需要用到的变量,而且你不能在不支持 stack 的硬件上实现这个函数。
如何查看内存泄露?
其实就是观察内存的变化。
有两种方法,一种是观察 swap 空间的大小。
/usr/sbin/swap -s total
通过隔一段时间就输入一次上面的命令,来观察 swap 空间是否变小,来判断是否内存泄露了。
你还可以使用 netstat 或者 vmstat 这些监控工具来查看。
如果你看到被使用的内存在不停的增加,那么就占用内存增长的最多的那个进程,就很有可能是内存泄露的来源。
内存的一些知识
1、
每一个 byte 都有他自己的 virtual address ,也就是虚拟地址。
2、
bytes 只能在内存里面被引用
3、
只有 VM manager 才知道一个 byte 是在内存里面,还是在硬盘里面?
4、
唯一的引用你的 byte 的方法,是给他一个虚拟地址。然而你的 byte 必须要存在内存里面才能被引用,如果你的程序要使用到的 byte 不存在内存里面,那么 VM manager 会去硬盘里面寻找,找到的话,他会将硬盘里包含这个 byte 的 page 给引进 memory 里面,如果他发现,memory 已经没有空间给他了,那么他会在 memory 里面找最老最没用的那个 page ,并且将这个 page 给抛出去,然后将那个 page 给引进来这个位置。
而你根本就不知道这个过程所发生的事情。
5、
你不能够引用别人的 virtual address ,因为 VM manager 知道这个 byte 是属于那个程序的,就算这两个程序的某一个 byte 使用了相同的 virtual address ,你也没法引用别人程序的 virtual address
指针和数组的规则总结
1、
访问 a[i] 的时候,经常被编译器编译成一个指针,那个代码等同于 *(a+i)
2、
Pointer 永远不会被重新翻译成数组,你可以定义一个指针,然后在这个指针后面使用 [i] 的方式,来给指针赋值,这样子做等同于使用数组。
数组在内存里面是怎么分布的?
数组的真实形态
pea[i][j]
会被编译器翻译成
*(*(pea+i)+j)
c 定义动态数组的方法
最核心的思想就是使用 malloc 函数,然后你会得到一个指向一些内存空间的指针,你将这个指针当成数组来用就行了。
#include <stdlib.h>
#include <stdio.h>
. . .
int size;
char *dynamic;
char input[10];
printf(“Please enter size of array: “);
size = atoi(fgets(input,7,stdin));
dynamic = (char *) malloc(size);
. . .
dynamic[0] = ‘a’;
dynamic[size-1] = ‘z’;
system call 和 library call 有什么不同?
file descriptor 和 file pointer 区别
file descriptor 在 Unix 里面是用来描述文件信息的。
file pointer 是保存了一个文件的指针。