工大后院

 找回密码
 加入后院

扫一扫,访问微社区

QQ登录

只需一步,快速开始

搜索
查看: 8727|回复: 30

关于C语言

[复制链接]
发表于 2008-3-12 02:34 | 显示全部楼层 |阅读模式
前段时间公司在组织C语言的培训,做了不少的题,发现还是学了不少的东西,
现在一项一项列出来.希望对人有帮助:

1. 关于char数组和字符串
其实字符串只是char数组的一个特例,但是应该常常涉及到字符串的结束符的问题,所以常常导致一些内存操作越界的问题.
a. 数组长度不够存放一个串
char a[5] = "hello"
如上所示,明显地不够长度去存放字符串"hello",这样的问题会很容易看到,但是如果是
char a[5];
strcpy(a, "hello");
这样会出更严重的问题,因为strcpy的库函数,他是以字符串的结束符'\0'为操作的条件的,在上面的这种条件下,会出现操作a数组后面的内存的情况,如果刚好后面的内存是有用到的,那么就出问题了,而且往往很难查(就是会操作a+5的内存,特别是字符串远远超过a的大小的时候,问题就严重了)之前的公司的一个死机的问题就是由于这样的操作出现问题的

b. 求char数组和字符串的长度
这个问题比较简单,只是要注意字符串的长度是以'\0'来计算的(可以参照string的多个函数都是以'\0'为判断条件,所以经常这些库函数是不安全的),如果中间出现了'\0'的情况的话,只会计算'\0'前面的长度
同时数组的大小并不是以'\0'来计算,应该是数组的实际的长度,所以应该分清楚 sizeof和strlen的时候的值是不同的

c. char数组的printf 格式为%s
如果以格式%s去printf一个普通的char数组,那么就会出现打印了正常的串之外可能会还有一些后续的乱码,那是因为这个数组有效的元素结尾不一定会是'\0',这里的%s格式打印是以这个字符串结符为判断条件的

评分

1

查看全部评分

 楼主| 发表于 2008-3-12 02:44 | 显示全部楼层

2. 关于大小端字节序

由于在网络中的设备可能会有不同的字节序的处理器,为了统一,在网络中都会把传输的数据转成大端字节的,这里的大端小端字节序就是相对于一个数据的字节的存储的内容的问题,具体的在TCP/IP详解的第一本的第一章还是前言就会讲到这个东西

字节序问题是经常让人晕的.我们经常在讨论这个问题,但是一直还会很晕,我现在的理解的方法就是假设一个数0x12345678,用一个32bit的空间来存储,那么,在大端的机器里面从低字节到高字节的内容是0x12,0x34,0x56,0x78,但是在小端机器的存储方式是0x78,0x56,0x34,0x12
但是要注意的是这里的地址是字节的地址,大小端字节序是跟字节的地址有关系,而不是字节里面的bit的关系

一般我们在都是在小端机器上运行程序,就算是涉及到网络通信,由于不是做底层的协议的传输之类的程序,所以这个基本上对于很多人来说是没有关系的,但是在通信和网络方面的设备来说,这个是很重要的,虽然可能下层的驱动方面已经做好这方面的转换的工作,但是如果从事这方面的开发的话一定要有这个观念,要分清楚大小端
回复

使用道具 举报

 楼主| 发表于 2008-3-12 04:42 | 显示全部楼层

3. 关于动态使用内存

全天下都应该记住,申请了一定要释放,可是却往往不释放这种情况还不停地出现.
a. 申请内存
使用malloc来申请,但是不能保证这个malloc是一定成功的,所以可以通过一个函数的封装来保护一下
bool new_malloc(size, **ptr);
在里面判断分配内在是否成功,然后在成功的时候把分配的内存的地址用ptr 记录下来,再返回一个成功的标志,然后在使用的时候就可以知道分配是否成功,而ptr指向的地址是否可用,同时在分配失败的时候可以保存ptr 的原来指向的地址

b. 释放内存
一般情况下,申请的内存一定要有一个指针能记录他的地址,否则没办法跟踪这个空间,当然也就没有办法去释放,所以最好就是在做动态分配的时候,专门用一个变量来存动态分配的这块内存的起始地址,同时在使用的时候用其他变量在内存块中移动,这样就可以最大限度保证动态分配的内存总是有记录的,

另外一种极端是,申请的内存被释放了,但是往往使用者没有把指针置空,然后又在后续的使用中用是否为空来判断这个空间是否还有效,引用已经释放的空间,这也是一个很土的方法,为了安全可以封装一个函数在里面把指针指向的内存释放,同时也把指针置空

[ 本帖最后由 jinry 于 2008-3-14 01:03 编辑 ]
回复

使用道具 举报

 楼主| 发表于 2008-3-12 04:46 | 显示全部楼层
我自己也没有记录这样一个提纲,后续会继续加的内容,如下所列,今天累了,先不写了..期待加精-.-
4. 位域
5. 字节对齐
6. union结构
7. 编程规范的一些要点
回复

使用道具 举报

发表于 2008-3-12 09:43 | 显示全部楼层
有PIC编程专用的经验吗?
回复

使用道具 举报

发表于 2008-3-12 09:59 | 显示全部楼层
不错,都是实用的高级基础知识
回复

使用道具 举报

发表于 2008-3-12 10:19 | 显示全部楼层
代码级测试时,每次只要见到malloc(strlen(str)),基本上就给它判死刑了。:D
回复

使用道具 举报

 楼主| 发表于 2008-3-12 22:14 | 显示全部楼层

4. 关于位域

位域,经常是在结构中指定某个元素使用一种数据类型的所占内存的几个bit, 指定的数据类型可以是char,int 或者short int多种的数据类型,
如unsigned int 4bitVar : 2就是说占这个的unsigned int的32bit中的2bit,但是在结构中现出多个位域的定义的时候,如下所示:
struct stAAA
{
        unsigned int  varA: 2;
        unsigned int  varB: 2;
        unsigned int  varC: 2;
}
就是都占 2 bit,但是三个元素都放在一个32bit的内存空间,还是放在三个不同的32bit空间里面,这是由编译器决定的,我曾经试过是gcc 和VC++的编译运行的结果是不一样的(有些书上是说这样的情况的会3个元素放在一个32bit的空间里面),所以一般情况下如果不是涉及到网络或者驱动等底层对bit进行处理的一般不要使用位域,因为内存空间使用的不同可能会导致比较难查的问题
回复

使用道具 举报

 楼主| 发表于 2008-3-12 22:47 | 显示全部楼层

5. 字节对齐

关于对齐其实是两个方面的注意,一个是使用指针时对内存地址的访问,一个是结构中对元素的排列的对字节对齐的影响.
这里默认是4字节对齐,因为基本上现在的编译器还是以4字节对齐为默认的对齐方式,不考虑1,2字节对齐的情况,如果在使用中要用到的话,可以用以下所列方式来指定
#pragma pack(n)
....
#pragma pack()

同时这里认为char是8 bit,short int 为16bit, 而int则为 32bit,同时CPU为32bit的

a. 地址访问
在默认的情况下,char 的访问是可以从任何地址开始的,一个字节,,short的则访问则需要从地上为2的倍数的地址开始访问,而int 的类型的变量则是从4的倍数的地址访问(如果不是从字节对齐的地址读取的话,不同的CPU可能有不同的处理,X86的好像是多次读内存然后经过处理转成要求的数据,但是对于ARM系统来说,可能就会定义成读错误了).如果不是对齐的,至少都会造成对存储器的多次访问,影响性能
通常情况下,编译的时候会把变量编成对齐的地址,但是如果对指针类型进行强制转换,比如char*的转成int*,那么就可能出现问题了,因为char*的可能在任意的地址,而转成int*的时候要求从4bytes的倍数地址开始访问内存

b. 结构内的元素的字节对齐
struct stAAA
{
        char  varA;
        short int  varB;
        char  varC;
        short int  varD;
        short int  varE;
        int  varF;
}
如上所示这样的结构的会要占16bytes的空间.第一个在varB时,不是2对齐的地上,所以在varA后会有一个字节的填充,然后加上varB刚好是4个字节对齐,然后varC和varD是一样的情况也是4字节,然后varE是可以从对齐地址开始,但是 varF又不可以所以在varE 后又要填充2个字节的内容.

这样的讲解应该是明白了,但是对于填充字节会填什么内容,鬼才知道,你能让自己的程序处于这种的不确定性中么?所以一般可以建议类型从小到大排列,像上面的可以排列成下面的样子可以省空间,如果不可避免的出现填充空间的情况,可以自己定义元素,专门只是用于字节对齐的填充
struct stAAA
{
        char  varA;
        char  varC;
        short int  varB;
        short int  varD;
        short int  varE;
        int  varF;
}
这样结构占12bytes空间.
回复

使用道具 举报

 楼主| 发表于 2008-3-12 22:58 | 显示全部楼层

6. 关于union

不用说,union结构的就是会点结构中的最大的元素的所占的空间,但是由于可以通过不同的元素访问同一块内存,这就往往会由于初始化不够完整出现严重问题
如下所示:
union unAAA
{
        int  varA;
        char arrB[2];
}
然后把数组arrB进行初始化,但是在使用的时候用varA去访问,这样程序就歇菜了,varA的另外两个字节的内容未知(但是到底是高地址的字节未知还是低地址的字节内容未知,可以看前面的大小端字节序的内容)
其实union我认为最要注意的也就是这个了,不同的元素访问的单位是不一样的,所以可能造成没被处理的内存空间的内存存在不可知的情况.,把程序写入一个未知的状态中,那么这个程序员很该死...
回复

使用道具 举报

 楼主| 发表于 2008-3-12 23:18 | 显示全部楼层

关于编程规范

编程规范,公司要求考试的,郁闷,100满分,90分及格,还有C语言考试,100满分,100分及格
简单列一下现在记得的要些比较有用的...有点凌乱.

1. 数据类型,一般用typedef把数据类型重命名一下,比如UINT32,表示是unsigned int 32bit的数据类型,然后在声明变量的时候变量名用s,uc,c,sz,st,p等前缀修饰,让人一看就知道变量的类型

2. 关于资源,不仅是内存,还有文件句柄,socket等要注意申请和释放的配对,特别是在申请了一样资源再申请另一项资源失败,然后退出处理的时候要注意把之前申请的资源也释放掉

3. 不要轻易使用除法,如果可以的话考虑其他方法来实现想要的功能,比如除数是定的,那么求出1/d,然后,每次的除法就是乘以这个数了,如果除数是常量,可是定义一个1/d的常量值,直接在编译的时候就确定了值了..

4. 循环变量是UINT或者UC类型的,不要使用<0 作为判断的条件,这会是一个死循环,这个说起来好像很容易记住,但是却有不少人做了这样的事情

5. 函数功能要单一,写着函数是干什么就是干什么的,不要再在里面进行其他的操作

6. 不要在.h文件里面定义全局变量, 同时我认为也应该尽量少地在.h文件里面用extern g_var这种方式,直接哪个文件需要用到就在哪个文件extern就好了,否则本来包含了这个头文件,但是不需要这个全局变量的.c文件也声明了这个变量


只能列到这么些了,其他的不知道怎么列,编程规范太多方面的东西.其他的想到再列...

[ 本帖最后由 jinry 于 2008-3-14 00:58 编辑 ]
回复

使用道具 举报

 楼主| 发表于 2008-3-12 23:19 | 显示全部楼层
原帖由 iptton 于 2008-3-12 09:43 发表
有PIC编程专用的经验吗?



我没做过PIC,但是现在的做的东西是嵌入式的linux下的.可能会跑在ARM上.....
回复

使用道具 举报

发表于 2008-3-13 12:37 | 显示全部楼层
3. 不要轻易使用除法,如果可以的话考虑其他方法来实现想要的功能,比如除数是定的,那么求出1/d,然后,每次的除法就是乘以这个数了

4. 循环变量是UINT或者UC类型的,不要使用<0 作为判断的条件,这会是一个死循环,这个说起来好像很容易记住,但是却有不少人做了这样的事情

这两条头次听到..新鲜...[em021] [em021]
回复

使用道具 举报

发表于 2008-3-13 16:18 | 显示全部楼层
第4条倒是常听说,第3条就觉得挺新鲜。学习了。
回复

使用道具 举报

发表于 2008-3-13 16:24 | 显示全部楼层
楼主如果能再讲讲理由,那就更好了。
回复

使用道具 举报

 楼主| 发表于 2008-3-13 22:47 | 显示全部楼层
-.-
除法和求余的操作,与加减乘的耗费的指令周期是多很多的,当然桌面软件可以不在乎,但是对于嵌入式软件,能省很多的,好像我看的那本ARM的一些东西说是,ARM本身不支持除法,由编译器的库支持,但是耗费指令周期是加法的20几倍

突然想起来,曾经为了一个问题的重现,需要在CPU的占用率在99%的情况下才有机率重现,于是构造这样CPU被占用99%的环境,用一个检验媒体文件数据的一个程序跑,很占运算量的,但是才占了,20%,然后我再多起几个这样的程序一起跑,由于进程的调度,加起来才30%,然后突然想起用浮点除.写了一个无限循环的浮点除,循环里面就是不停地除,结果CPU占用率超过99%(redhat linux, 用top命令查看进程占用CPU)

UINT , UC, 则是无符号数,无符号数不可能<0

[ 本帖最后由 jinry 于 2008-3-14 01:25 编辑 ]
回复

使用道具 举报

 楼主| 发表于 2008-3-13 22:48 | 显示全部楼层
下周要C语言考试,会要看看之前做的习题,有想到的再写上来....
回复

使用道具 举报

发表于 2008-3-13 23:04 | 显示全部楼层
[em021] 复习&学习
回复

使用道具 举报

发表于 2008-3-14 00:10 | 显示全部楼层
如果涉及变成规范的话,估计程序中就不能出现#pragma 了。
因为C标准中明确写着标准中没有#pragma .
回复

使用道具 举报

发表于 2008-3-14 00:58 | 显示全部楼层
[em021] [em021]  感谢jinry.
学习了.
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入后院

本版积分规则

QQ|Archiver|手机版|小黑屋|广告业务Q|工大后院 ( 粤ICP备10013660号 )

GMT+8, 2024-4-27 19:48

Powered by Discuz! X3.5

Copyright © 2001-2024 Tencent Cloud.

快速回复 返回顶部 返回列表