C语言中的小启发(陆续更新。。。)

1. NULL 、NUL 、EOF、'\0'

(1) NUL是ASCII字符集中‘\0’的名字,NUL占用一个字节,该字节模式:00 00 00 00。可以用于结束ASCII字符串,但是在c/c++中没有定义,如果要使用的话需要自定义为

#define NUL '\0'

(2) NULL表示空指针,哪里也不指向的特殊的指针值

例如:

char *ptr = NULL //ptr是空指针

在stdio.h中的表示是:     

/* Define NULL pointer value */
#ifndef NULL
  #ifdef __cplusplus
    #define NULL   0
  #else
    #define NULL  ((void *)0)
  #endif
#endif

(3)EOF通常定义为-1,文件结束标志,一般是Ctrl+z;对于二进制文件可调用feof()函数;

        在stdio.h中的表示是:

#define EOF (-1)

(4)'\0'是字符串结束标识符,只用于判断一个字符串是否结束

2. 操作符的结合性

疑问:解释它是什么以及什么时候需要知道它

回答:它是仲裁者,在几个操作符具有相同的优先级时决定先执行哪个!

例子:  

int a;
int b = 1, c = 2;
a = b = c;
printf("a=%d b=%d c=%d\n",a,b,c);

结果:

a = 2 b= 2 c= 2

分析:

a = b = c;等价于a = b; b = c;

那么先执行a = b呢?还是先执行 b = c呢?

若先执行a = b,则a = 1;

若先执行b = c,则a = 2;

因为所有的赋值符都具有右结合性,最右边操作符最先执行,然后从右到左;说明先执行 b = c;结果为2

3.无符号类型的使用

    建议:尽量不要在代码中使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为(如年龄)不存在负数而用它表示数量,尽量使用像int的有符号类型,这样在涉及混合类型的复杂细节,不必担心边界问题。

例子:

int arr[]={1,2,3,4,5,6};
#define TOTAL_ELEMENT (sizeof(arr)/sizeof(arr[0]))//sizeo返回值为unsigned类型

int _tmain(int argc, _TCHAR* argv[])
{
	int d =-1;
	int x;
	/*.其它相关代码.*/
	if (d <= TOTAL_ELEMENT-2)//signed int和unsigned int测试相等性,d被升级为unsigned -1变成巨大的整数
	{
		x=arr[d+1];
	}
	else
	{
		printf("error\n");//执行printf语句;结果是error
	}
	return 0;
}

 4.堆栈段

      段内包含一种单一的数据结构----堆栈。它是一块动态内存区域,实现了一种“先进后出”的结构,入栈操作使堆栈变长,出栈操作从堆栈中取出一个值。事实上绝大部分处理器,堆栈是向下增长的,即朝低地址方向生长。堆栈段有三个主要的用途,两个与函数有关,另一个跟表达式计算有关。

   4.1 堆栈为函数内部声明的局部变量提供空间

   4.2 进行函数调用时,堆栈存储与此有关的一些维护性信息

   4.3 堆栈可以用作暂时的存储区。有时候程序需要一些临时存储,可以把部分计算结果压倒堆栈中,当需要时再把它从堆栈中取出。

数据段和bss段的整个区域统称为数据区

5.过程活动记录

    c语言自动提供的服务之一就是跟踪调用链---哪些函数调用了哪些函数,当下一个“return”语句执行后,控制将返回何处等。解决这个问题的经典机制是堆栈中的过程活动记录。当每个函数被调用时,都会产生一个过程活动记录。过程活动记录是一个数据结构,用于支持过程调用,并记录调用结束以后返回调用点所需的全部信息。结构如下图:


                                                                                                                               过程活动记录结构

6.堆

         堆区域用于动态分配的储存,也就是通过内存分配函数(malloc、calloc、realloc)获得内存,并通过指针访问。堆中的所有东西都是匿名的---不能直接按名字直接访问,只能通过指针间接访问。

       动态分配函数返回void*

       malloc函数分配size字节的存储区。

       calloc函数分配n个数据项的内存连续单元,每个数据项大小为size,且在返回指针之前先把分配好的内存的内容清零,c分配后clear。

       realloc函数改变已分配存储区的大小,可以扩大也可以缩小,它经常把内存拷贝到别的地方然后将指向新地址的指针返回。

       free函数把内存返回给堆

注意:被分配的内存总是经过对齐的,以适应机器上最大尺寸的原子访问,一个malloc请求申请的内存大小为方便起见一般被圆整为2的乘方。


7.内存泄漏 

        由于C语言通常并不使用垃圾收集器(自动确认并回收不再使用的内存块),在日历管理器、邮件工具、操作系统本身经常需要数日乃至数周连续运行,并需要动态内存的分配和回收。那么在使用malloc()和free()时要非常慎重。堆经常会出现的两种类型问题:

       1.释放或改写仍在使用的内存(内存损坏)

       2.未释放不再使用的内存(内存泄漏),每当调用malloc分配内存时,注意要调用相应的free来释放它。


8.常见段错误的原因

        8.1坏指针值错误

        1.在指针赋值之前就用它来引用内存;2.向库函数传递一个坏指针;3.对指针进行释放之后再访问它的内容,可以修改free语句,在指针释放之后再将它置为空值;
free(p);
p = NULL;

8.2改写(overwrite)错误

越过数组边界写入数据,在动态分配的内存两端之外写入数据,改写一些堆管理数据结构

p = malloc(256);
p[-1] = 0;
p[256] = 0;

8.3指针释放引起的错误

1.释放同一个内存块两次,或释放一块未曾使用malloc分配的内存;2.释放仍在使用的内存,或释放一个无效的指针
例子:
for(p = start; p; p = p->next)       
{
      ....
      free(p);//会在下一次循环迭代时,程序会对已经释放的指针进行解引用操作,导致不可预料的错误
}

       在遍历链表时正确的释放元素的方法是使用临时变量存储下一个元素的地址。这样就可以安全地在任何时候释放当前元素,不必担心在取下一个元素的地址时还要引用它。代码如下:

struct node *p, *start, *tmp;
for(p = start; p; p= tmp;)
{
   tmp = p->next;
   free(p);
}

9.声明与定义

       首先,C语言中的对象必须有只有一个定义,但它可以有多个声明;这里的对象只是跟链接器有关的“东西”,如函数和变量。

定义:

       它创建了一个新对象,并确定对象的类型和分配内存;

int my_array[100];//定义数组

声明:描述对象的类型,用于指代其它地方定义的对象;声明可以出现多次。

extern int my_array[];//描述在其它地方定义了数组,等价于extern int my_array[100]

10.C语言数据类型



11.数组和指针参数是如何被编译器修改的

      “数组名被改写成一个指针参数“规则并不是递归定义的,数组的数组会被改写为”数组的指针“,而不是”指针的指针“。
                                     实      参                                                   所匹配的形式参数
        数    组                        char c[10]                                                         char *c;                   指针

       数组的数组                  char c[8][10];                                                    char(*)[10];              数组指针

       指针数组                      char *c[15];                                                      char **c;                   指针的指针
     
      数组指针(行指针)          char (*c)[64]                                                     char (*c)[64]            不变

      指针的指针                   char **c;                                                            char ** c;                 不变

12. 函数中的数组参数

      在声明函数时,数组作为函数参数,可以不指定数组的长度
利:
      因为不论调用函数的程序传递给它的数组参数长度是多少,这个函数都将照收不误,这就允许函数操作任意长度的一维数组。
弊:
      函数无法知道该数组的长度,有可能造成操作越界。因此,如果需要知道数组长度,长度值必须作为函数的一个参数传递给函数。  









posted @ 2014-03-20 15:54  小怪兽&奥特曼  阅读(158)  评论(0编辑  收藏  举报