硬件中最小的单位:高低电平;也就是软件中的位。
一、关键字
关键字:编译器预先定义的具有一定意义的字符串。
它就是字符串,只不过被赋予了特定的意义。
C语言有32个关键字。
如:sizeof
是关键字,而不是函数。
我们没有实现sizeof
,而是编译器规定好的供我们查看内存空间容量的一个工具。
printf
是标准库里面的一个函数,借助于操作系统。如果嵌入式开发中没有OS,直接进行裸板操作,就不能调用该函数。但是仍然可以使用sizeof()
查看内存空间容量大小。
二、数据类型
计算机:CPU与很多资源,中间通过数据总线和地址总线相连。
C语言的操作对象/目标:资源/内存(硬件资源(显存)、LCD缓存、LED灯、其他的串口和I2C设备等)。
C语言如何描述这些资源的属性?
通过资源属性的大小——即数据类型关键字:它是限制内存(土地)大小的关键字。
因此,要记住每个数据类型的大小。
涉及到数据类型大小,一般情况下都不是定值,与编译器有关,由编译器决定。
1、char
硬件芯片操作的最小单位。bit:1和0;高低电平;位。
char buf[x];
能更好地处理硬件所发送或接收的数据。
如果软件操作的最小单位和硬件一样,那开发量会非常庞大。因此软件采用了集合方式,把一组bit当作软件操作的单位:8bit == 1Byte (1B),字节
ASCII码表:在整个计算机的键盘中,用8bit不同的状态可以描述所有的键值。
2、int
其大小就是编译器最优的处理大小,即系统一个周期所能接收的最大处理单位。
比如:
- 32位系统,一个周期能处理的数据是32bit。则:32b(4B)就是int的大小,此时编译器最优。
- 16bit的单片机系统,则int大小就是2B(16bit),此时编译器最优。
如果是数字的处理(循环、计数器等),用int比用char好。
3、short
如果没有特殊要求,用int而不用short;除非对空间有非常强烈的限制,比如在32位电脑中,必须要求空间是16bit处理,则使用short。
4、long、long long
它们是C语言可扩展的类型,要看编译器是否支持。
三、自定义数据类型
C编译器默认定义的内存分配不符合实际资源的形式。
自定义=基本元素的集合
1、struct:元素之间的合集
struct myabc
{
unsigned int a;
unsigned int b;
unsigned int c;
unsigned int d;
};
// 这是声明,只是告知编译器,具有内存空间定义的能力,但是还没有进行定义。
struct myabc mybuf; // 这是定义
注意:结构体中顺序是有要求的。
2、union:共用起始地址的一段内存
在一些技巧型的代码中会用到。
union myabc{
char a;
int b;
}; // 声明
union myabc abc; // 定义
3、enum:枚举(整型常数的集合)
定义常数:
#define MON 0
#define TUE 1
#define WED 2
enum与define是一致的,只不过enum可以更好地描述一个对象集合。
enum 枚举名称{常量列表};
enum abc {MOD, TUE, WED}; //不管定义多少个,一次只取一个,存在32bit的空间中。
用处:比如,一个芯片中有一些数字1、2、3,另一个芯片中也有一些数字1、2、3,但是代表不同的芯片不同的含义,因此两个芯片分别定义一个enum,就可以用一些独有的名字来代表1、2、3,让人一看就明白。
enum就是一个让程序员与程序员交流上更加便利的一种打包的常量集合的概念。
例子:
/*001.c*/
#include <stdio.h>
enum abc {MOD=100, TUE, WED};
int main()
{
enum abc a1=MOD;
printf("the a1 is %u:%d\n",sizeof(a1),a1);
printf("the %d\n",MOD);
return 0;
}
/*002.c*/
#include <stdio.h>
enum abc {MOD=100, TUE, WED};
int main()
{
enum abc a1 = 800;
printf("the a1 is %u:%d\n",sizeof(a1),a1);
printf("the %d\n",MOD);
return 0;
}
4、typedef
见:《typedef(C语言)》
四、类型修饰符
对内存资源在内存中的存放位置的限定。
对于嵌入式开发工程师而言,内存分配的具体位置实际上与某些代码和流程非常有关系。
在内核和驱动编程中,类型修饰符会被大量地使用。
要对类型修饰符有比较深入的总结才行。
1、auto
是一种默认情况,分配的区域是在可读可写的内存上。
auto int a;
auto long b;
区域如果在{ }
中,则是栈空间。
2、register
分配的区域是在寄存器上。
因此,register可以定义一些快速访问的变量。即,如果某个变量访问的频率很高,就使用register修饰,可以尽量放在寄存器中。
寄存器:CPU缓存器
内存:内部存储器
register int a;
// 编译器不是一定会把a放在寄存器中
// 编译器会尽量地安排CPU的寄存器去存放a。但如果寄存器空间不足时,a还是会被存放在存储器中
register只是告诉编译器尽量去安排,但是如果确实寄存器空间不足时,它是没有任何办法的,所以它没办法一定能确定存储的位置,因此,&
取地址符号对register不起作用。
中看不中用!
// 003.c
#include <stdio.h>
int main()
{
register int a;
a = 0x10;
printf("a is %d\n", a);
return 0;
}
// 004.c
#include <stdio.h>
int main()
{
register int a;
a = 0x10;
printf("a is %d\n", &a);
return 0;
}
register虽然功能不是很强大,它无法确定一定会存储在寄存器上;但是当程序员看到register修饰的变量后,就能够知道一点:该变量的访问一定很频繁!
3、static
在多文件工程编程中,在内核中,会大量地看到static在用。
在嵌入式C中的3种应用场景(修饰3种数据):
(1)修饰函数内部的变量:局部变量
int fun()
{
int a; ===> static int a;
}
(2)修饰函数外部的变量:全局变量
int a; ===> static int a;
int fun()
{
}
(3)修饰函数
int fun(); ===> static int fun();
函数也可以理解成是一种变量。
// 005.c
#include <stdio.h>
int main()
{
printf("main is %d\n", main);
return 0;
}
4、extern
外部声明。主要在全局变量和函数中使用。
5、const
本意:用于定义常量(经const修饰就不能改变了)
实际:只读的变量(这个变量还是可以通过某些方法来改变的)。
const是C语言的软肋!中看不中用!!
const int a = 100;
// 编译器不允许进行显式地修改
a = 200;
// 但是还是有办法来对a进行修改的
-
内存泄漏
char *p; // 这2个等价 const char *p; // 建议用它 char const *p; // 建议p指向的内存空间,内容是只读的。 // p是1字节1字节(char)去读的。p可以指向任意空间,但是只要指向了,就建议它的内容是只读的。 // ——可理解为:字符串。 // 这2个等价 char * const p; // 建议用它 char *p const; // 建议p永远指向第一次指向的地址。它指向固定地址,但是地址里面的内容是可以变的。 // 一般这种定义,都是硬件资源的定义。比如,缓存地址是焊好的,或者芯片做的时候就做死了,但是里面的内容是可以变的。 const char * const p; // 限制更加严格。它指向的地址不能变,里面的内容也不能变。 // 工程中使用得不多,硬件中描述比较多。比如,描述ROM设备
例1:
// 001.c
#include <stdio.h>
int main()
{
char *p = "hello world\n"; // 双引号表示整型常量,里面的内容是不可变的
printf(" the first world is %x\n", *p);
*p = 'a';
printf("p is %s\n", p);
}
例2:
// 002.c
#include <stdio.h>
int main()
{
char buf[] = {"hello world\n"};
char *p = buf;
printf("the first world is %x\n", *p);
*p = 'a';
printf("p is %s\n", p);
}
例3:
// 003.c
#include <stdio.h>
int main()
{
const char *p = "hello world\n"; // 直接用const修饰只读内容
printf(" the first world is %x\n", *p);
*p = 'a';
printf("p is %s\n", p);
}
例4:
// 004.c
#include <stdio.h>
int main()
{
const int a = 0x12345678;
int b = 0x11223344;
int *p = &b;
*(p+1) = 0x100; // p+1:a的地址
printf("a is %x\n", a);
}
const只是建议,不是强制。只是编译时不会变,运行时如果通过越界访问,也可以改变。
6、volatile
它不是限定内存存放位置的修饰符,而是告知编译器编译方法的修饰符。
告知编译器:不优化编译。
volatile的使用与地址关系比较大;在嵌入式和驱动开发中,与硬件打交道比较多。在上层开发中,几乎看不到这个关键字。
volatile修饰变量的值的修改,不仅仅可以通过软件,也可以通过其他方式(硬件外部的用户)。
例子:键盘按下,值改变,LCD亮。
volatile char *p;
*p == 0x10; // 会一直从里面读,而不会优化掉。