[关闭]
@SiberiaBear 2015-10-14T13:10:58.000000Z 字数 5995 阅读 1561

个人C语言的一些盲区

C语言

固定链接:https://www.zybuluo.com/SiberiaBear/note/171060


1.

学习前辈的一个bug,在注释一行后的末尾不能加‘\’,‘\’的意思是该行没结束,则注释会把下一行也注释掉,但是软件不会显示下一行被注释了。例如:

  1. main()
  2. {
  3. // this is a comment and with a endding of \
  4. other_codding();
  5. }

这个程序的注释(第一行)将第二行的函数也一起注释了,但是软件没有用颜色标示下边也被注释。

2.

单片机软件编译过程中有这么个问题,我用的是CCS,当你写如下代码:

  1. main()
  2. {
  3. int a=0;
  4. while(1)
  5. {
  6. a = a + 1;
  7. }
  8. }

软件在编译时会将“a = a + 1;”这句话直接忽视掉,不编译。原因是a的变化并未对单片机产生任何影响,只对本身产生影响,所以不编译,被优化掉。

3.

在程序for循环中,递减循环要比递增循环更好一些,适应递减,更容易被操作系统或单片机监测。

  1. main()
  2. {
  3. int a;
  4. for(a=100;a>0;a--); //better
  5. }

4.

C语言在同一运算符中的多个操作数之间未规定计算顺序(&&、||、?:和,运算符除外),所以可能会出现错误。

printf("%d %d\n",++n, power(2,n));

在这个运算中,可能先计算++n,再调用power(),也可能先调用power(),在计算++n。所以会出现错误。再如:

x = acc() + mic();

在这个运算中,可能先调用acc(),再调用mic(),或者相反,如果两个函数之间存在互相的全局参数调用修改,可能会出现错误。

5.

将外部变量的声明与定义严格区分开来很重要。变量声明用于说明变量的属性(主要是变量的类型),而变量定义除此之外还将引起存储器的分配。

int sp;
double val [MAXVAL];

extern int sp;
extern double val []

前边两条语句是变量定义,会为之分配存储单元,同时这两条语句为该源文件中其余部分的声明。
后边两条语句声明外部变量,val 数组的长度在其他地方确定。

6.

static关键字可以用于声明外部变量、函数或内部变量。当static用于声明外部变量时,这样的变量将不能被同一程序的其他文件所访问,而只能被被声明的该文件内的函数所访问,起到隐藏外部变量的功能。例如:当file.c文件中声明了两个外部变量与函数,若想只允许该函数使用这两个外部变量而不允许main.c文件使用,则可以将这两个外部变量声明为静态变量,这样这两个外部变量将只对于该文件内有效。
同理,static也可以用于声明函数。通常来说,函数是可以全局访问的,如果把函数声明为static类型,则该函数只在该函数声明所在的文件内可见,在其他文件内均无法访问。
当static用于声明内部变量时,则该变量只能在该函数中有效,但它与自动变量不同的是,不管函数是否存在,它将始终占用内存,不会随着函数被调用与退出而存在或消失。
static修饰内部静态变量时的作用域也在函数内部,虽然函数调用结束不会被销毁,但静态变量在函数之外不可见。

7.

自动变量,是指在定义它们时才创建,在定义它们的函数返回时系统将回收该变量所占用的内存空间。一般,不做专门说明的局部变量都是自动变量,用auto关键字说明,可省略。自动变量只有一种存储方式,就是存储在栈中,所以它的作用域只在函数内。

8.

宏定义的两个不清楚的地方:①宏定义中的形式参数不能用带引号的字符串替换。如:

  1. #define f(b) printf("b" "%d",b)

b参数将只替换printf函数中第二个b;如果必须要替换,则在替换的文本行中,参数以#作为前缀,例如:

  1. #define f(b) printf(#b "%d", b)

这样,当使用语句f(mm)调用该宏时,将被扩展为:

  1. printf("mm" "%d", mm)

②预处理运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。如,使用paste连接两个参数:

  1. #define paste(front, back) front ## back

当使用past(name, 1)宏调用时,结果将是name1

9.

指针和数组的一个区别。如下两句定义

char amessage[] = "now is the time";    //定义一个数组
char *pmassage = "now is the time";     //定义一个指针

上述声明中,amessage是一个仅仅足以存放初始化字符串以及空字符'\0'的一维数组。数组中的单个字符可以进行修改,但amessage始终指向同一个存储位置。另一方面,pmessage是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址,但如果试图修改字符串的内容,结果是没有定义的。

10.

如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数。数组的行数没有太大关系,因为前面已经讲过,函数调用时传递的是一个指针,它指向由行向量构成的一维数组。如,假设tab[2][13]是一个二维数组,将它作为参数传递给函数func(),那么func()可以写成:

func(int tab[2][13]){...}

或者

func(int tab[][13]){...}

或者

func(int (*tab)[13]){...}

最后一种声明形式表示参数是一个指针,它指向具有13个整型元素的一维数组。因为方括号的优先级高于*的优先级,所以上述声明中必须使用圆括号。如果去掉括号,变成:

func(int *tab[13]){...}

这相当于是声明了一个数组,该数组有13个元素,其中每个元素都是一个指向整型对象的指针。
一般说,多维数组中除数组的第一维(下标)可以不指定大小外,其余各维都必须明确大小。

11.

指向函数的指针的声明为:

int (*func)(void);

而下句声明:

int *func(void);

则表示func是一个函数,该函数返回int类型的指针。

12.

条件编译语句#if中不能使用sizeof,因为预处理器不对类型名进行分析。但预处理器并不计算#define语句中的表达式,因此,在#define中使用sizeof是合法的。

13.

两个指针的加法是非法的,而指针之间的减法运算却是合法的,如:定义两个指针分别指向一个数组的始末:

  1. char *low, *high;
  2. mid = (low+high) / 2; //这样计算中间指针是行不通的
  3. mid = low + (high - low) / 2; //但这样算是可以的

14.

typedef#define的区别:typedef只是为了增加可读性而为标识符另起的新名字,而#define原本在C中是为了定义常量的,到了C++,也逐渐成了起别名的工具。有时很容易搞不清楚两者的区别。比如:

  1. typedef int INT
  2. #define INT int

这两条语句的功能是一样的。然而,第一条语句更规范,因为在早期的许多C编译器中第二条语句是非法的,只是在现今编译器中进行了扩充。为了兼容,一般都遵循#define定义“可读”的常量以及一些宏语句的任务,而typedef则常用来定义关键字、比较长的类型的别名。比如:

  1. typedef (int*) pINT;
  2. #define pINT2 int*;

这两条语句的差别很大,实践中:定义pINT a,b;,意思等同于int *a, *b;,而定义pINT2 a,b;,意思则是int*a,b;,是不同的。

15.

文本流由一系列行组成,每一行的结尾是一个换行符。如果系统没有遵循这种模式,则标准库将通过一些措施使得该系统适应这种模式。例如,标准库可以在输入端将回车符和换行符都转换为换行符,而在输出端进行反向转换

16.

在头文件<stdio.h>中,如getchar()putchar()等“函数”,其实都是宏定义,这样可以防止在处理每个字符时都要调用一遍函数,缩减了程序开销。

17.

对于标准输出函数printf()中格式转换符%的一些说明:
%后边接有如下几种符号(按顺序依次,除s外均为可选):
- -,代表将要输出的字段左对齐,如果没有该负号符,则默认右对齐;
- 数字*,代表最小输出的位数,如果将要输出的字段的长度大于该数字,则输出全部的长度字段,如果小于该数字,则输出该数字长度的字段,除输出规定字段长度以外,其余的空位用空格符填充,如果是左对齐则从右边开始填充,如果是右对齐,则从左边开始填充。可以理解为开辟输出空间。对于*,它的意思是该字段位数由后边对应顺序的变量决定,如

  1. printf("%*s", max, s)

表示输出指定的s字符串,最小输出位数由max决定;
- .,作用是分隔最小输出字段数字与精度数字;
- 数字*,代表输出数字或字符串的精度,如果将要输出的字符串小于该数字,则输出字符串实际全部内容,如果大于该数字,则仅输出该数字指定的位数大小,从最右边开始取舍。若输出浮点数,则代表输出小数点后的位数,若输出整型数,则代表最小输出的数字位数。可以理解为直接截取输出字段。*与上文的相同,如

  1. printf("%.*s", max, s)

表示输出指定的s字符串,输出精度由max决定;
- 字母h或l,字母h表示将整数作为short类型打印,字母l表示将整数作为long类型打印;另,l也可以在输出浮点型数据时使用,用于
- s,代表该格式转换符将转换输出的是字符串。

另,如果%后边紧接着一个%,则不作为特殊格式来判断,直接打印一个%

18.

启动一个C语言程序时,操作系统环境负责打开3个文件,并将这3个文件的指针提供给该程序。这3个文件分别是标准输入、标准输出和标准错误,相应的文件指针分别为stdin,stdout,stderr,它们在<stdio.h>中声明。在大多数环境中,stdin指向键盘,而stdoutstderr指向显示器,但是,stdinstdout可以被重定向到文件或管道。

19.

要注意,函数int getc(FILE *fp)int putc(int c,FILE *fp),是对文件的操作。另外,char *gets(char *s)int puts(const char *s)是操作stdinstdout,而对文件中字符串的操作函数是char *fgets(char *line, int maxline, FILE *fp)int fputs(char *line, FILE *fp)。另,gets函数在读取字符串时将删除结尾的换行符('\n'),而puts函数在写入字符串时将在结尾添加一个换行符。

20.

函数malloc()和calloc用于动态的分配存储块。函数malloc声明如下:

void *malloc(size_t n)

当分配成功时,它返回一个指针,设指针指向n字节长度的未初始化的存储空间,负责返回NULL。函数calloc的声明如下:

void *calloc(size_t n, size_t size)

当分配成功时,它返回一个指针,该指针指向的空闲空间足以容纳由n个指定长度的对象组成的数组,否则返回NULL。该存储空间被初始化为0.

21.

关于无用变量会导致空指针检查失效的例子。

  1. extern void bar(void);
  2. void foo(int *x)
  3. {
  4. int y = *x; /* 1 */
  5. if(!x) /* 2 */
  6. {
  7. return; /* 3 */
  8. }
  9. bar;
  10. return;
  11. }

该例子中,不管x是否为空指针,函数bar都会被调用,并且程序不崩溃。首先,由于第1句话,编译器会认为x不会是一个空指针,故而第2句话判断x是否为空指针的语句会被认为无用代码优化掉,同时第3句话也不会执行;另外,由于*x并不是volatile类型,所以未使用的 变量y会被编译器直接优化掉,所以函数bar将必定会被执行。

22.

一个指针循环:

  1. int *zp, *xp, *yp;
  2. for(i = 0; i< 10; i++)
  3. {
  4. *zp++ = *xp + *yp;
  5. }

和另外一个指针循环:

  1. int *zp, *xp, *yp;
  2. int tmp = *xp + *yp;
  3. for(i = 0; i< 10; i++)
  4. {
  5. *zp++ = tmp;
  6. }

有什么不同?
答:不同,当指针zp不会与xp或yp指向同一片内存空间时是相同的,一旦zp == xpzp == yp时,结果就不同了。

23.

size_t类型很重要,他可以存储下任何对象的大小值,所以用他定义数组长度的存储变量很重要。size_t是函数sizeof的返回值类型,也是如strlen()等字符串长度计算函数的返回值类型,在一般机子上是unsigned int

24.

注意在函数调用中的逗号符号,如func(a(), b()),其中的逗号并不是逗号运算符,因此不能保证运算的顺序,a()b()会随机的顺序计算。

25.

无符号整数的上限溢出是确定的,如UINT_MAX+1 = 0;但有符号整数的上限溢出是不确定的,取决于编译器。

26.

关于函数返回值为指针类型的分析。

  1. char *getstring(void)
  2. {
  3. char p[] = "hello";
  4. return p; /* 编译器一般会提示警告*/
  5. }
  6. void main(void)
  7. {
  8. char *str = NULL;
  9. str = getstring();
  10. printf("%s", str);
  11. }

首先分析,在函数getstring()中,定义的变量p属于局部变量,当函数调用结束后将释放,所以在主函数中检查返回的指针,是找不到应该指向的内容的。有以下几种解决办法:
(1)使用全局数组;
(2)在函数getstring()中使用newmalloc在堆上动态分配内存,但是需要在主函数中使用了该返回值后,释放内存空间,否则会造成内存泄漏,分别用delete() free()释放。使用delete时,会调用类的析构函数,而free则不会。
(3)定义为静态类型static char p[] = "hello";。这样请避免在主函数中多次调用该函数,否则,一个地方的调用修改了该函数中的指针变量,在另外一个地方会受到影响,不是C应该提倡的。
(4)用String类型,会对指针所指向的字符串进行值拷贝,而不是传递内存地址,奈何在C++中才有。
(5)使用字符串常量const char *p = "hello";。字符串常量存储在静态存储区域,所以,虽然随着函数调用结束,指针p所占用的内存会被释放,但p所指向的字符串常量内存并不会释放,而持续到程序结束,这样,函数返回的地址就是实际存放字符串的地址。如果使用p[]来定义就不同了,p本身就是字符串,随着函数调用的结束,p这片内存就会被释放,数据也就会丢失。
参考:http://blog.chinaunix.net/uid-20788636-id-1841283.html

27.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注