C专家编程(Expert C Programming)(二)

C专家编程(Expert C Programming)(二)

1、程序内存布局

C专家编程(Expert C Programming)(二)C专家编程(Expert C Programming)(二)

2、运行时数据类型

堆栈 函数内部的局部变量,函数调用时的维护性信息,暂时存储区(通过alloca分配的内存就是位于此,它将被下一个函数调用覆盖)

我们可以用如下程序测试堆栈的起始地址:

int main()

{

int i;

printf

("%p",&i);

}

堆栈是向下生长的(向低地址)。

活动记录(activation record)

当每个函数被调用时,都会产生一个过程活动记录。

C专家编程(Expert C Programming)(二)

    活动记录中的静态链接指向从词法上讲属于外层过程的活动记录,由编译器决定。动态链接则是活动记录指针链,运行时指向最靠近自己的前一个过程调用的活动记录。活动记录可能并不位于堆栈中,可能是寄存器中。

把变量声明为static,则其保存于数据段中而不是堆栈中。

int main()

{

static int i;

printf("%p",&i);

}

我们可以看到这个程序和上面那个程序的结果是不一样的。

数据

3、控制线程

    在进程支持不同的控制线程,只要简单的为每个控制线程分配不同的堆栈就可。在各个线和的堆栈间有一个red zone页。

setjmp and longjmp

setjmp

int setjmp( jmp_buf env );

Saves the current state of the program.

setjmp returns 0 after saving the stack environment. If setjmp returns as a result of a longjmp call, it returns the value argument of longjmp, or if the value argument of longjmp is 0, setjmp returns 1. There is no error return.

The setjmp function saves a stack environment, which you can subsequently restore using longjmp. When used together, setjmp and longjmp provide a way to execute a “non-local goto.” They are typically used to pass execution control to error-handling or recovery code in a previously called routine without using the normal calling or return conventions.

A call to setjmp saves the current stack environment in env. A subsequent call to longjmp restores the saved environment and returns control to the point just after the corresponding setjmp call. All variables (except register variables) accessible to the routine receiving control contain the values they had when longjmp was called.

setjmp and longjmp do not support C++ object semantics. In C++ programs, use the C++ exception-handling mechanism.

longjmp

void longjmp( jmp_buf env, int value );

The longjmp function restores a stack environment and execution locale previously saved in env by setjmp. setjmp and longjmp provide a way to execute a nonlocal goto; they are typically used to pass execution control to error-handling or recovery code in a previously called routine without using the normal call and return conventions.

A call to setjmp causes the current stack environment to be saved in env. A subsequent call to longjmp restores the saved environment and returns control to the point immediately following the corresponding setjmp call. Execution resumes as if value had just been returned by the setjmp call. The values of all variables (except register variables) that are accessible to the routine receiving control contain the values they had when longjmp was called. The values of register variables are unpredictable. The value returned by setjmp must be nonzero. If value is passed as 0, the value 1 is substituted in the actual return.

Call longjmp before the function that called setjmp returns; otherwise the results are unpredictable.

Observe the following restrictions when using longjmp:

· Do not assume that the values of the register variables will remain the same. The values of register variables in the routine calling setjmp may not be restored to the proper values after longjmp is executed.

· Do not use longjmp to transfer control out of an interrupt-handling routine unless the interrupt is caused by a floating-point exception. In this case, a program may return from an interrupt handler via longjmp if it first reinitializes the floating-point math package by calling _fpreset.

· Be careful when using setjmp and longjmp in C++ programs. Because these functions do not support C++ object semantics, it is safer to use the C++ exception-handling mechanism.

#include "setjmp.h"

jmp_buf buf;

void banana(){

printf("in banana()\n");//2

longjmp(buf,1);

printf("not see because  i longjum");

}

int main()

{

if(setjmp(buf))

  printf("back in main\n");//3

else{

 printf("first time\n");//1

 banana();

}

}

常于错误恢复,有C++中用catch,throw

4、附加的虚拟内存紧随当前堆栈的尾部映射到地址空间中。

5、有用的C语言工具有cb,indent,cddel,等。

6、标准的代码优化技巧包括:消除循环,函数代码就地扩展,公共子表达式消除,改进寄存器分配,省略运行时对数组边界的检查,循环不变量代码移动,操作符长度削减(指数操作变为乘法操作,乘法操作变为移位操作或加法操作)等。

7、内存的一个段,Intel系列是以64KB为单位的区域。far关键字表示指针存储了段寄存器的内容和便移地址,near关键字只存储16位偏移地址,他的段使用当前的数据段或堆栈段寄存器。

在规范形式下,指针的偏移地址的范围是0~15.

存在,看见,真实的

不存在,看见,虚拟的

存在,看不见 透明的

不存在,也看不见 擦除了。

C专家编程(Expert C Programming)(二)

    虚拟内存通过“页”形式组织。进程只能操作位于物理内存中的页面。

int main()

{

int MB=0;

int i=1;

while(malloc(i<<20))  ++MB;

printf("allocate %d MB total\n",MB);

}

看一下可以分配多大的内存空间。注意,在上述表达式中,i的值是不会变的,只是创建新的临时变量。

所有的对内存的读写操作都要经过cache。

C专家编程(Expert C Programming)(二)

    填充于同一cache行的主存地址恰好都是该cache行大小的正数倍。

void *memcpy(void *dest, const void *src, size_t n)

src 源字符串,n 拷贝的最大长度,dest 目的字符串,指向dest的指针

string.h | mem.h

void *memset(void *s, int c, size_t n)

s 要设置的字符串,c 设置的内容, n 长度

字符串中的n个字节内容设置为c

void *memmove(void *dest, const void *src, size_t n)//字符串的拷贝

int memcmp(const void *s1, const void *s2, size_t n)//比较两个字符串的长度

8、数据段和堆

    如同堆栈可以自动增长,数据段也包含一个对象:堆。堆中的所有东西都是匿名的,只能通过指针间接访问。分配堆的唯一方法是:用malloc,calloc(分配同时清空为0),realloc(更变内存大小)等。

    被分配的内存总是经过对齐,以适合机器的原子访问。一个malloc请求申请的内存大小一般为2的乘方。堆的末端用break来标识。通过brk,sbrk(调整数据段的大小至一个绝对值)来移动break指针。

C专家编程(Expert C Programming)(二)

9、内存泄漏

调用malloc分配内存,要用free来释放。可以用alloca来分配内存,在离开时,他所分配的内存会自动释放。

11、总线错误

信号就是一种事件通知或一个软件中断。对齐(alignment)就是数据项只能存储在地址是数据大小整数倍的内存位置上。数据项不能跨越页面或cache边界。

union{

char a[10];

int i;

}u;

int main()

{

int *p=(int*)&(u.a[1]);

*p=17;

}

说明:这个问题,在现在编译器上可能不会再存在了。

int *p=0;

*p=17;

通常导致错误有如下原因

非法值指针:还没有赋值就用它来引用内存,释放内存后再访问它的内存。(定义和释放都赋值为0),越过了数组边界,同一内存释放了两次,或释放无效的指针。如下一个例子

for(p=start;p;p=p->next)

  free(p);

p都释放了,也就无从谈再释放了。应当如下写

for(p=start;p;p=tmp){

   tmp=p->next;

   free(p);

 }

空指针

#include "stdio.h"

#include "stdlib.h"

#include "setjmp.h"

#include "signal.h"

jmp_buf buf;

void handler(int s)

{

if(s==SIGINT) printf("got a SIGINT signal\n");

longjmp(buf,1);

}

int main()

{

signal(SIGINT,handler);

if(setjmp(buf)){

printf("back in main\n");

return 0;

}//if

else printf("first time through\n");

loop:

goto loop;

}

说明:系统并不支持有信息处理程序内部调用库函数。

12、字符常量的类型是int

C专家编程(Expert C Programming)(二)