OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2911|回复: 5

C语言高手之路(二)

[复制链接]

10

主题

62

帖子

0

精华

初级会员

Rank: 2

积分
152
金钱
152
注册时间
2016-9-6
在线时间
25 小时
发表于 2016-9-19 17:29:40 | 显示全部楼层 |阅读模式
五、数组&字符串&结构体&共用体&枚举
5.1、c语言中定义一个字符串:
char a[6]={'l','i','n','u','x','\0'};    '\0'的字符编码为0就是NULL;也就是说内存中遇到0,翻译成字符是就是'\0',或这是NULL;
char a[6]="linux";
char *p="linux";

5.2、 sizeof(a)=6是运算符,其意思是所占空间大小,包括字符串后面的‘\0',strlen(a)=5是一个函数,其意思是字符串的长度。strlen( p);其中p只有是字符指针变量才有意义,       它的形参是数组变量是没有意义的,因为strlen是根据什么时候遇到 '\0',才结束测试字符串的长度,就算数组不初始化也是有长度的。
char *p="linux"; sizeof(p)永远等于4,因为p是指针变量,存的是地址。所以总结:sizeof()是拿来测数组的大小,strlen()是拿来测试字符串的长度。

5.3、结构体用 . 或者是 ->访问内部变量,其实质是用的指针访问。如
struct student
{
      int a;
      double b;
      char c;
}s1;
则s1.a =12;实质就是int *p=(int *) &s1;*p=12    首先a是int 型,所以是强制类型 int * ,其次是就是算地址,然后强制类型,地址应该是int 型然后加减,不然的话,系统
  s1.b=12.2;实质就是double *p= (double *) ((int)&s1+4),*p=12.2;   不知道是以int 型加减还是以float型加减,还是以char型加减,所以 应当 (int)&s1; 而且因为地址是
  s1.c=c;实质就是 char *p=(char *) ((int)&s1+12); *p=c;                  4字节的,所以必须是int型。
  
5.4、对齐方式:
(1)猜测如果是32位系统,那么编译器默认是4字节对齐,64位系统,那么编译器默认是8字节对齐,因为32位或64位一次性访问效率是最高的。
(2)<1>结构体首地址对齐(编译器自身帮我们保证,会给它分配一个对齐的地址,因为结构体自身已经对齐了,那么第一个变量也就自然对齐,所以我们才可以想象成第一个变量从0地址存放);
       <2>结构体内部的各个变量要对齐。
       <3>整个结构体要对齐,因为定义结构体变量s1时,再定义变量s2时,如果s1没有对齐,就坑了s2,所以也要保证整个结构体对齐。
       无论是按照几字节对齐,我们都可以联想到内存实际的安排。1字节对齐那么不管int float double 类型,在每4个格子的内存挨着存放。2字节对齐,也是一样的
想法,举一个列子,如果第一个变量是char 型,第二个变量是int型,那么0地址存放char型,1地址空着,2地址存放int型地址部分,3地址存放int型地址部分,然后
上排最右4、5地址存放int型高址部分。4字节对齐,如果第一个变量是char型,第二个变量是int型,那么0地址存放char型,1,2,3地址空着,从4地址开始存放int,
最后给变量分配完内存空间后,必须要保证整个结构体对齐,下一个结构体的存放起始地址是n字节对齐整数倍,如是4字节对齐,那么最后short算成4字节 以保证整个结构体对齐。
整个结构体对齐,如2字节对齐(2的整数倍),只要是0、2、4地址就行了,如果是4字节对齐(4的整数倍),就必须是0、4地址。8字节对齐(8的整数倍)
(3)猜测4字节/8字节其实是针对int型/double型的,比如0地址是char型,那么4字节对齐,int型、float型就必须从4地址开始存放,那么8字节对齐,int型就必须从4地址存放,double型就必须从8地址开始存放.小于几字节对齐的那些,如char型和short型只要能按照规则存放就行了。
(4)对齐命令:<1>需要#prgama pack(n)开头,以#pragma pack()结尾,定义一个区间,这个区间内的对齐参数就是n。(不建议使用)
如:s1占5个字节,s2占8字节(默认)
        #pragma pack(1)
       struct stu1
        {
                         (结构体本身以及变量) 对齐规则:2字节对齐(2的整数倍),只要是0、2、4地址就行了,
                                            4字节对齐(4的整数倍),就必须是0、4地址,
                               8字节对齐(8的整数倍),就必须是0、8、16
          char c;
          int a;
          //short d;
         }s1;
                                                                                                                                                                          
   struct stu2  {
              char c;
               int a;
                //short d;
    }s2;
           <2>gcc推荐的对齐指令__attribute__((packed))  __attribute__((aligned(n))),在VC中就不行,没有定义这个命令
           (1)__attribute__((packed))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。packed的作用就是取消对齐访问。
           (2)__attribute__((aligned(n)))使用时直接放在要进行内存对齐的类型定义的后面,然后它起作用的范围只有加了这个东西的这一个类型。它的作用是让整个结构体变量     整体进行n字节对齐(注意是结构体变量整体n字节对齐,而不是结构体内各元素也要n字节对齐,内部元素按照默认对齐方式)
                                                   
5.5、offsetof宏:#define offsetof( TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)   
(1)offsetof宏的作用是:用宏来计算结构体中某个元素和结构体首地址的偏移量(其实质是通过编译器来帮我们计算)。
(2)offsetof宏的原理:我们虚拟一个type类型结构体变量,然后用type.member的方式来访问那个member元素,继而得到member相对于整个变量首地址的偏移量。
(3)学习思路:第一步先学会用offsetof宏,第二步再去理解这个宏的实现原理。
      (TYPE *)0  这是一个强制类型转换,把0地址强制类型转换成一个指针,这个指针指向一个TYPE类型的结构体变量。 (实际上这个结构体变量可能不存在,但是只要我不去解引用这个指针就不会出错)。
      ((TYPE *)0)->MEMBER(TYPE *)0是一个TYPE类型结构体变量的指针,通过指针指针来访问这个结构体变量的member元素,然后对这个元素取地址,又因为 改地址是从0地址开始算的,所以这个地址就是相对起始地址的偏移量。

5.6  container_of宏: #define container_of(ptr, type, member) ({\
                                const typeof(((type *)0)->member) * __mptr = (ptr);\
                                (type *)((char *)__mptr - offsetof(type, member)); })      两条语句;,然后用{ } ,\表示提示编译器本行因为屏幕不够,链接下一行。用#(也就是宏定义)
时,如果本行不够要用 \ 提示编译器接着是下一行的。必须要用   \ ,猜测因为宏定义一行就算结束了。

(1)作用:知道一个结构体中某个元素的指针,反推这个结构体变量的指针。有了container_of宏,我们可以从一个元素的指针得到整个结构体变量的指针,继而得到结构体中其他元素的指针。
(2)typeof关键字的作用是:typepef(a)时由变量a得到a的类型,typeof就是由变量名得到变量数据类型的。
(3)这个宏的工作原理:先用typeof得到member元素的类型定义成一个指针,然后用这个指针减去该元素相对于整个结构体变量的偏移量(偏移量用offsetof宏得到的),减去之后得到的就是整个结构体变量的首地址了,再把这个地址强制类型转换为type *即可。

5.7 p是一个地址,(int)p+6 和(char *)+6;效果是一样的,第一种是将地址p当作int型加减,第二种是将地址p做为char *指针,他每次加减都是一字节一字节相加减的,如果是 (int *)P+6,那么他每次加减都是按照4字节一跳。就相当于加了+4*6;

5.8  小端模式:变量的高地址存放在高地址,低地址存放在低地址;   通信模式也要分大小端,先发送/接受的是高地址还是低地址,
     大端模式:变量的高地址存放在低地址,低地址存放在高地址;
测试:有用共用体 union 和指针方式来测试,基本思路是让 int a=1; 看低地址 char 是0还是1 ;变量都是从地址开始存放,只是变量的高地址和低地址先存放谁不确定。
不能用位与来测,因为存放和读取都是按照某一个方式来的,结果永远都是一样的。int a=1; char b=(char)a;这种方式不可以测试,因为不管大小端,它都以变量a的低地址部分赋给b;
   
5.9、枚举类型(int型):                                                         这样写默认从第一个常量开始是0,1,2,3,4.........
                                                                                   也可以自己赋值,但是每一个值是不一样的,否则逻辑上出错。
                  

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

10

主题

62

帖子

0

精华

初级会员

Rank: 2

积分
152
金钱
152
注册时间
2016-9-6
在线时间
25 小时
 楼主| 发表于 2016-9-19 17:30:23 | 显示全部楼层
管理员没人管理啊,这个发帖的字数限制是个硬伤啊。稍微多一点内容就不行了。@管理员
回复 支持 反对

使用道具 举报

2

主题

90

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1107
金钱
1107
注册时间
2014-8-9
在线时间
192 小时
发表于 2016-9-19 17:52:46 | 显示全部楼层
顶~~~~好货
回复 支持 反对

使用道具 举报

84

主题

766

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
2775
金钱
2775
注册时间
2015-6-1
在线时间
394 小时
发表于 2016-9-19 18:56:01 | 显示全部楼层
关注一下,你这个更适合博客,排版还好看些。
自在随心
回复 支持 反对

使用道具 举报

10

主题

62

帖子

0

精华

初级会员

Rank: 2

积分
152
金钱
152
注册时间
2016-9-6
在线时间
25 小时
 楼主| 发表于 2016-9-20 10:20:24 | 显示全部楼层
yuzeyuan1 发表于 2016-9-19 18:56
关注一下,你这个更适合博客,排版还好看些。

在博客里面我有很多高亮的容易分辨。这边就没想那么多了。
回复 支持 反对

使用道具 举报

126

主题

820

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1683
金钱
1683
注册时间
2012-10-28
在线时间
62 小时
发表于 2016-9-21 09:04:47 | 显示全部楼层
1838039453 发表于 2016-9-19 17:30
管理员没人管理啊,这个发帖的字数限制是个硬伤啊。稍微多一点内容就不行了。@管理员

你可以做好文檔,上傳,謝謝!
坚持学习!就能成功!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-5-25 06:02

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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