APUE中文第三版勘误

本博客根据自己对APUE中文第三版的阅读理解,列出其中自己认为翻译错误或者翻译有歧义的部分。

1,page81

中文:其中,如果测试文件是否已经存在,mode就为F_OK;否则mode是图4-7中所列常量的按位或。
英文(page102):The mode is either the value F_OK to test if a file exists, or the bitwise OR of any of the flags shown in Figure 4.7.

2,page89

中文:对于符号链接,文件长度是在文件名中的实际字节数。
英文(page111):For a symbolic link, the file size is the numberof bytes in the filename.

Makefile

1,makefile的规则

1
2
3
4
5
6
7
8
9
target ...: prerequisites ...
cmd
...
...
其中,target可以是一个obj file,也可以是一个执行文件,还可以是一个label.
prerequisites:为生成该target所依赖的文件或target
cmd(必须以一个Tab键作为开头):为target要执行的命令
注:prerequisites中如果有一个以上的文件比target文件要新的话,cmd所定义的命令就会被执行。

IPC-标识符重用

1,槽位使用情况序列号

ipc_perm结构中含有一个名为seq的变量,是一个槽位使用情况序列号。该变量是一个由内核为系统中每个潜在的IPC对象维持的计数器。每当删除一个IPC对象时,内核就递增相应槽位的使用情况序列号,若溢出则循环回0.

简单理解就是: 存在ipc_perm[max],当创建ipc对象时,在ipc_perm数组中查找第一个ipc_perm元素为空,记为ipc_perm[i].当删除ipc_perm[i]对应的ipc时,ipc_perm[i].seq++,下次再次创建ipc对象时,假设ipc_perm[i]元素为空,则返回ipc id为i+ipc_perm[i].seq*max.
注:槽位使用情况序列号是一个跨进程保持的内核变量。

2,示例代码

2.1 创建ipc,然后立即删除该ipc

对应的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*************************************************************************
> File Name: p26_1.c
> Author: qinchao
> Mail: 1187620726@qq.com
> Created Date:2017-04-07 Time:11:02:25.
************************************************************************/
#include<stdio.h>
#include<sys/msg.h>
#include<sys/types.h>
int main()
{
int i,msgid;
for(i=0;i<10;i++)
{
msgid=msgget(IPC_PRIVATE,0666|IPC_CREAT);
printf("msgid=%d\n",msgid);
msgctl(msgid,IPC_RMID,NULL);
}
return 0;
}

对应的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
bogon:unp qinchao$ gcc p26_1.c -o p26_1
bogon:unp qinchao$ ./p26_1
msgid=196608
msgid=262144
msgid=327680
msgid=393216
msgid=458752
msgid=524288
msgid=589824
msgid=655360
msgid=720896
msgid=786432
bogon:unp qinchao$

即:创建ipc1之后,ipc_perm[0]维护该ipc对象的相关信息,然后删除ipc1,ipc_perm[0].seq++;当创建ipc2时,仍然是ipc_perm[0]维护ipc2相关信息。

奇怪的死循环

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
int main()
{
int i;
int a[10];
for(i=0;i<=10;++i)
{
a[i]=0;
printf("%d\n",a[i]);
}
return 0;
}

该程序对应的汇编代码见如下代码:

C++对象模型-关于对象

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,关于对象

从这篇博客开始真正介绍C++对象模型,前边BB了那么多没用的,终于开始了C++对模型的分析。关于C++对象模型的介绍,我将根据《深度探索C++对象模型》这本书,其书中的每一章,对应一篇博客,博客内容为自己对这本书的理解和补充吧。

1.1C语言中的struct

我们知道,C语言是面向过程的,即数据和处理数据的函数时分开的,也就是说,struct中不能包含函数(当然也不能包含static变量)。但是我们可以在struct中声明指向函数的指针来模拟数据和处理数据的函数指针。其中,需要指出的是,c语言中struct变量的数据成员的访问方式都是基于该结构体变量的首地址的偏移量来访问的(同样适用于C++中的class/struct)。
即,见如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
struct point3d;
typedef void (*initfun)(struct point3d*);
struct point3d{
float x;
float y;
float z;
initfun init;//指向初始化数据函数的指针
};
void initp(struct point3d* p3d) //struct point3d变量初始化函数
{
p3d->x=1;
p3d->y=2;
p3d->z=3;
printf("%f %f %f\n",p3d->x,p3d->y,p3d->z);
return;
}
int main()
{
struct point3d pd;
pd.init=initp;//将initp函数地址赋给结构体中指向函数的指针pd.init。
pd.init(&pd);
}

C++对象模型-3

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,switch/if-else

上一节 数组的内存布局。本文准备介绍一下switch/if-else的实现机制。

1.1swich实现机制
1.2 if-else实现机制
  • 待续

基础类型的内存表示

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,基础类型内存表示

上一节 介绍了swich/if-else的实现机制。本文准备介绍一下基础类型的内存表示。

1.1大端法和小端法

小端法,即数字的低位在低地址。大端法,即数字高位在低地址。大小端法的最小单位为byte,而不是bit.
举例:

1
2
3
4
5
int i=-16;
对应的小端法表示为(从低地址到高地址(从左到右为地址增长方向)对应的每个byte):
0xf0 ff ff ff
大端法表示为(从低地址到高地址(从左到右为地址增长方向)对应的每个byte):
0x FF FF FF F0

在实际开发中,可以需要做大小端法互换,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void swap1(int *rhs)
{
unsigned char *p=rhs;
unsigned char tmp;
tmp=p[0];
p[0]=[3];
p[3]=tmp;
tmp=p[1];
p[1]=[2];
p[2]=tmp;
return ;
}

在小端机上,如下程序的输出结果为-16.

1
2
3
4
5
6
7
8
9
10
int main()
{
int i;
unsigned char *p=(unsigned char*)&i;
p[0]=0xF0;
p[1]=0XFF;
p[2]=0XFF;
p[3]=0Xff;
printf("%d\n",i);
}

动态/静态数组内存布局

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,数组内存布局

上一节 简单介绍了结构体作为函数参数和返回值的情况。本文准备介绍一下数组的内存布局,即静态数组/动态数组和一维数组/二维数组,顺便介绍一下0长度数组的妙用。

1.1静态一维数组和动态二维数组

静态一维数组,即类似于int a[10];动态数据,即类似于int p=(int)malloc(10sizeof(int));(或者int p=new int[10]);

1.1.1静态一维数组
1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
int main()
{
int a[3];
a[0]=1;
a[1]=2;
a[2]=3;
printf("%d %d %d\n",a[0],a[1],a[2]);
}

对应的汇编代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
.file "c2-0.c"
.section .rodata
.LC0:
.string "%d %d %d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $1, 20(%esp) //esp+20即为p[0]的地址
movl $2, 24(%esp) //esp+24即为p[1]的地址
movl $3, 28(%esp) //esp+28即为p[2]的地址
movl 28(%esp), %ecx
movl 24(%esp), %edx
movl 20(%esp), %eax
movl %ecx, 12(%esp) //将ecx压栈,即[esp+12]=ecx
movl %edx, 8(%esp) //将edx压栈,即[esp+8]=edx
movl %eax, 4(%esp) //将eax压栈,即[esp+4]=eax
movl $.LC0, (%esp) //类似于“%d %d”字符串的地址
call printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",@progbits

main函数执行时,栈帧为:
img0

结构体变量作为函数参数和返回值

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,结构体类型作为函数参数和返回值

上一节 简单介绍了基本的函数调用过程,即栈帧。本节介绍结构体类型作为函数参数和返回值的情况。

1.1结构体变量的内存布局

结构体变量的数据成员之间的内存空间是连续的,结构体中的每个成员都是通过相对于结构体变量首地址的偏移量来访问。假设有如下结构体:

1
2
3
4
5
6
7
8
9
10
11
12
struct test{
char k;
int i;
short j;
};
如果想获得k变量的偏移量可以通过如下方式:
struct test* p=NULL;
(unsigned int)&(p->k);
等价于: (unsigned)(&(((struct test*)0)->k));
或者等价于:
struct test x;
(unsigned int)((unsigned char)&(x.k)-(unsigned char)&(x));

struct test x;其中x结构体变量的内存布局为:
其中空白表示该字节为对齐字节,即没有用到;i0表示i变量的第一个字节,以此类推。
struct

函数调用过程(栈帧)

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,函数调用过程

今天先介绍下基本的函数调用过程,即栈帧。

1.1栈帧

每个函数调用都对应一个栈帧。每个栈帧由ESP和EBP寄存器来确定。每个函数执行时,其局部变量都是在自己对应的栈帧内分配内存。假设A函数调用B函数,此时正在执行B函数,需要指出的是,当执行完当前函数B后,返回调用函数A,此时执行函数B时,为B函数的局部变量分配的的内存空间也就不存在了。也就是说,函数返回值不能是函数体内局部变量的地址,也不能是局部变量的引用。即如不能出现如下两种形式之一: