C专家编程——读书笔记

来源:互联网 发布:土木工程网络教育本科 编辑:程序博客网 时间:2024/05/16 11:38

1、总线错误与段错误

bus error(core dumped) 总线错误(信息已经转储)
segmetation fault(core dumped)段错误(信息已经转储)
这种错误相信只要你在unixcc++这两种错误是常见又是很头疼的错误,来看看作者怎么解说的:
产生原因: 当硬件告诉操作系统一个有问题的内存引用时,操作系统通过发送信号给有问题的进程进行交流。(信号是一种事件通知或一个软件中断)。普通进程一般对“总线错误”或“段错误”信号将进行信息转储并且终止,俗称“程序coredump”。(当然可以设置一个信号处理程序修改程序的缺省反应)。

总线错误:
引起原因: 几乎总是由于对未对齐的读或写引起的。它之所以称为总线错误是因为对未对齐的内存访问时,被阻塞的组件就是地址总线。
对齐(alignment)数据项只能存储在地址是数据项大小的整数倍的内存位置上,这样可以加速内存访问。如:访问一个8字节的double的数据时,地址只能是8的整数倍,所以存储一个double的地址只能是248008,但不能存储于地址1006因为它不能被8整除,只要保证这个原则,就可以保证一个原子项数据不会跨页或cache块的边界。
引起总线错误的小程序:
union 
{
    char a[10];
    int i;
}u;
int *p =(int*)&(u.a[1]);
*p =17;/*p中未对齐的地址将会引起总线错误*/
因为数组和int的联合确保了a是按照int4字节来对齐的,所以“a+1”肯定不是int来对齐的。(后面专门讨论下内存对齐问题)

段错误:
   引起直接原因:
1)解除引用一个包含非法值的指针。
2)解除引用一个空指针(常常是从系统返回,却未经过检查)。
3)未得到正确权限进行访问。如:向只读文本段存储值就回引起段错误。
4)用完了堆栈或堆空间。

  出现频率来分:
1 坏指针值错误:指针赋值前用它来引用内存;或向库函数传递坏指针(系统程序出现坏指针问题很可能还是出自自己的代码中);指针释放后还访问他的内容,
指针释放后,一定要把它置为NULL
2 改写错误:越过数组边界写入数据,在动态分配内存两端写数据,或改写一堆管理数据结构(在动态内存两端写数据就会引起这种错误)。
    p= malloc(256); p[-1]=0; p[256]=0;         
3 指针释放引起的错误:释放一个内存块两次,或释放一个未曾用malloc分配的内存,或释放正在使用中的内存,或释放一个无效的指针。
    如:
        for(p = start; p; p= p->next)
           {
             free(p);
           }
存在程序对下一次循环中对释放的指针p进行解除引用。
4用dbx可以看出堆栈空间是否用完:
dbx a.out
(dbx) catch SIGSEGV
(dbx) run
...
signal SEGV(segmentation violation ) in <some_routine> at oxeff57708
(dbx)where
如果可以看到调用链说明堆栈空间还没有用完。如果看到以下情景:
fetch at oxeffe7a60 failed --- I/O error
(dbx)
那么堆栈空间已经用完,上面16禁止数就是可以提取或影射的堆栈地址。
C-SHELL中可以通过limit stacksize 10 来调整堆栈的空间为10K,进程的总线地址空间仍然收到交换区大小的限制,可以用swap -s命令查看交换区的大小。

 

2C语言中有限状态机

1)在状态中判断事件,适合于FPGA类硬件程序

cur_state = nxt_state;
switch(cur_state) //在当前状态中判断事件
{                 

cases0:  //s0状态
           if(e0_event) //如果发生e0事件,那么就执行a0动作,并保持状态不变;

{            
             执行a0动作;
             //nxt_state = s0;      //因为状态号是自身,所以可以删除此句,以提高运行速度。
     }

    else if(e1_event)  //如果发生e1事件,那么就执行a1动作,并将状态转移到s1态;

{                   

执行a1动作;
              nxt_state = s1;
     }
     else if(e2_event)  //如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;

{        
               执行a2动作;
               nxt_state = s2;
       }

break;

cases1: //s1状态
          if(e2_event) //如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;

{                             

执行a2动作;
                nxt_state = s2;
      }

break;

cases2:  //s2状态
          if(e0_event)   //如果发生e0事件,那么就执行a0动作,并将状态转移到s0态;

执行a0动作;
                 nxt_state = s0;
       }

}

2)在事件中判断状态,适合于软件程序

//e0事件发生时,执行的函数

void e0_event_function(int * nxt_state)

{

    int cur_state;

    cur_state = *nxt_state;

switch(cur_state)

{

        case s0: //观察表1,在e0事件发生时,s1处为空

        case s2:

           执行a0动作;

           *nxt_state = s0;

      }

}

//e1事件发生时,执行的函数

void e1_event_function(int * nxt_state)

{

    int cur_state;

    cur_state = *nxt_state;

switch(cur_state)

{

        case s0: //观察表1,在e1事件发生时,s1和s2处为空

           执行a1动作;

           *nxt_state = s1;

     }

}

//e2事件发生时,执行的函数

void e2_event_function(int * nxt_state)

{

    int cur_state;

    cur_state = *nxt_state;

    switch(cur_state){

        case s0: //观察表1,在e2事件发生时,s2处为空

        case s1:

           执行a2动作;

           *nxt_state = s2;

    }

}

 

3、整形提升

定义:

1)只要一个表达式中用到了整型值,那么类型为char,short,int(这几者带符号或无符号均可)的变量,以及枚举类型的对象,都可以被放在这个整型变量的位置。

2)如果1中的变量的原始类型的值域可以被int表示,那么原值被转换为int,否则的话被转换为unsigned int

以上两者作为一个整体,被称为整型提升

 

整型提升的概念容易与普通算术类型转换产生混淆。这两者的区别之一在于后者是在操作数之间类型不一致的情况下发生,最终将操作数转换为同一类型。而在算术运算这种情景下,即使操作数具有相同的类型,仍有可能发生整型提升。

例如:

char a,b,c;

c=a+b;

在上述过程中,尽管两个运算符"+""="的操作数全为char型,但在中间计算过程中存在着整数提升:对于表达式a+b ab都是char型,因此被提升至int型后,执行“+”运算,计算结果(int)再赋值给c(char),又执行了隐式的类型转换。

最后提一句,ANSI C规定,编译器如果能保证运算结果一致,也可以省略类型提升的操作----这通常出现在表达式中存在常量操作数的时候。

 

另外几个例子:

char a;

printf(“sizeof(a)=%d”, sizeof(a));

输出:1

原因:a不是一个表达式,a是char型,char型占1字节。

 

char a, b;

printf(“sizeof(a+b)=%d”, sizeof(a+b));

输出:4

原因:a+b是一个算术表达式,a、b均整型提升(int型),所以占4个字节。

 

char a, b, c;

printf(“sizeof(c=a+b)=%d”, sizeof(c=a+b));

输出:1

原因:表达式c=a+b中,a和b是算术运算,因此整型提升(int型),计算结果(int型)再赋值给c(char型),又执行了隐式的类型转换,所以最终占1字节。

  

printf( "%d",sizeof ('A') );

输出:4

原因:’A’其实也是一个表达式,提升为整型,所以占4个字节

以上总之:要看sizeof里面的内容,如果是类型则占一个字节,如果是表达式,则占4个字节

  

4、二维指针

#include<stdio.h>

#include<malloc.h>

int main()

{

    int **p;

    int i,j;

    p=(int **)malloc(4*sizeof(int *));//申请一个行指针

    if(p==NULL) return;

    for(i=0;i<4;i++)

    {

    p[i]=(int *)malloc(5*sizeof(int));//申请一个列指针

    if(p[i]==NULL) return;

    }

    for(i=0;i<4;i++)

      for(j=0;j<5;j++)

          p[i][j]=i*5+j;

    for(i=0;i<4;i++)

    {

       for(j=0;j<5;j++)

         printf("%5d",p[i][j]);

         printf("\n");

    }

return 0;

}

 

5、fall throughcase后面不加break,就依次执行下去

6、多行写入

1)在行末加\,后续的字符串在下一行顶行写

printf(“this is a new book \

may be helpful to you”);

2)现在可以用一连串相邻的字符串常量来代替他,他们会在编译的时候自动合并

除了最后一个字符串之外,其余每个字符串末尾的’\0’字符会被自动删除

printf(“this is a new book”

“may be helpful to you\n”);

 

7、Static

函数的定义和申明默认情况下是extern的,全局可见,但静态函数只在申明他的文件当中可用,不能被其他文件所引用

静态函数的好处:1)其他文件中可以定义相同名字的函数,不会发生冲突

2)静态函数不能被其他文件所用

3)静态函数被自动分配在一个一直使用的存储区,直到退出应用程序,避免了调用函数时压栈出栈

 

对于函数来讲,Static的作用是隐藏,所有未加static前缀的全局变量和函数都具有全局可见性,其他的源文件也能访问。

对于变量来说,static还有两个用:保持变量的持久性和默认初始化为0

 

8Interpositioning:用户编写和库函数同名的函数并取而代之的行为

C语言中不支持函数重载VC++6.0可以编译是因为他按照C++的方法去编译。

要实现同名

1)可以在函数前面加static,只在本文件中可见,

2)或者在编译的时候不把头文件添加进来,

3)或者添加头文件,且自定义函数名完全和库函数名一样,完全覆盖库函数

 

9、C语言运算符优先级

口诀:

圆号框号,箭头一句号

后加后减非反,前加前减负正

指针地址长度

乘除加减左右移,小等大等等等不等

按位与按位或,逻辑与逻辑或

赋值逗号

单目条件赋值右结合

 

10#definetypedef的区别

1Typedef是一种彻底的封装类型,在申明他之后不能再往里面增加别的东西,而#define对其他类型符对宏名进行的扩展。

#define peach int 

unsigned peach i;//没问题


typedef int banana;

unsigned banana i;//错误,非法

2)其次,用typedef定义的类型能够保证申明中的所有变量均为同一种类型,而用#define定义的类型则无法保证。

#define int_ptr init *

int_ptr chalk,cheese;

经过宏扩展第二行变成int *chalk,cheese;这使得chalkcheese成为不同的类型,chalk是指向int的指针,而cheese是一个int

typedef char * char_ptr;

char_ptr Bentley,Rolls_Royce;

BentleyRolls_Royce的类型依然相同,虽然前面的类型名变了,但他们的类型相同,都是指向char的指针。

总之,#define是宏扩展,而typedef是彻底的类型的封装

 

11、 定义和申明

定义:只能出现在一个地方,确定对象的类型分配内存,用于创建新对象,如int my_array[100]

申明:可以多次出现,描述对象的类型,用于指代其他地方定义的对象,如extern int my_array[]

申明他所说明的并非自身,而是描述其他地方创建的对象,定义相当于特殊的申明,他为对象分配内存。

 

12、数组指针、指针数组、指针的指针

数组指针(也称行指针)

定义 int (*p)[n];

()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

 

如要将二维数组赋给一指针,应这样赋值:

int a[3][4];

int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。

 p=a;        //将该二维数组的首地址赋给p,也就是a[0]&a[0][0]

 p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。

 

指针数组

定义 int *p[n];

[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]p[1]p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。

如要将二维数组赋给一指针数组:

int *p[3];

int a[3][4];

for(i=0;i<3;i++)

p[i]=a[i];

这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]p[1]p[2]

所以要分别赋值。

 

这样两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。

还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。

比如要表示数组中ij列一个元素:

*(p[i]+j)*(*(p+i)+j)(*(p+i))[j]p[i][j]

 

优先级:()>[]>*

原创粉丝点击