111
1.创建C程序
(1)创建C语言程序文件。
vim 001.c
(2)进入后是" REPLACE "模式,需要键盘输入" I "进入" INSERT "模式。
(3)程序写完后按" ESC "退出输入模式。
(4)输入" :wq "。
(5)接下来通过gcc编译。
gcc -o 001 001.c //链接文件
./001 //执行,可执行目标文件
2.编译过程
(1)预处理:去掉注释、加载头文件、替换宏定义,不会进行语法检查。
gcc -E -o a.i 001.c
(2)编译。
gcc -S -o a.s a.i
gcc -S -o a.s 001.c //一步到位
(3)汇编。
gcc -c -o a.o a.s
gcc -c -o a.o 001.c //一步到位
(4)链接。
gcc -o build a.o
gcc -o build 001.c //一步到位
3.编译错误
(1)预处理错误
#include "name" :自己定义的头文件,当前目录下找,即与" 001.c "在同一目录下。
#include <name> :系统目录下找。
若新建的头文件 "abc.h" 在文件夹 " inc " 中,即不与" 001.c "在同一目录下,可用下面命令:
gcc -I ./inc -o build 001.c
(2)语法错误
(3)链接错误:原材料不够。例如:
#include "stdio.h"
#include "abc.h"
void fun(void);
int main()
{
int a = ABC;
printf("Hello World! \n");
fun();
return 0;
}
error:undefined reference to 'fun'
fun函数在abc.c中:
void fun(void)
{
}
我们可以这样做:
gcc -I ./inc -o build 001.c abc.c //不建议
/* 生成汇编文件 */
gcc -I./inc -o a.o 001.c
gcc -I./inc -o b.o abc.c
/* 链接 */
gcc -o build a.o b.o
寻找标签(即函数)是否实现了,链接时是否一起加入链接。
多了。
error:multiple definition of 'fun'
多次实现了标签,只保留一个标签实现。
4.预处理使用
(1)包含类
#include 包含头文件 -- 头文件包含进来在当前的位置进行展开 -- 展开的最终目的是把头文件进行一次编译
(2)宏(所有宏名全都是以大写字母为标识符)
#define 宏 -- 替换(不进行语法检查)(语法检查在编译时进行)
#define 宏名 (宏体)(在宏名和宏体之间至少一个空格)(加括号)
例如:
#define ABC 5 + 3
printf(" %d \n",ABC * 5); // 5 + 3 * 5
#define ABC (5 + 3)
printf(" %d \n",ABC * 5); // (5 + 3) * 5
/* 宏函数 */
#define ABC(x) (5 + (x)) // 参数加括号,整体加括号
(3)条件编译
#ifdef
#else
#endif
(4)预定义宏
系统定义好的,C语言编译器定义好的,跟具体操作系统关系不大,在工程调试应用众多。
这些宏名全部都由两个下划线组成,首位相组合,又称其为系统定义宏。
__FUNCTION__ : 函数名
__LINE__ :行号
__FILE__ :文件名
1 #include <stdio.h>
2
3 int main()
4 {
5 printf("the %s,%s,%d\n",__FUNCTION__,__FILE__,__LINE__);
6
7 return 0;
8 }
/* 输出 */
the main , 002.c , 5
1 #include <stdio.h>
2
3 int fun()
4 {
5 int a;
6
7 printf("the %s,%s,%d\n",__FUNCTION__,__FILE__,__LINE__);
8
9 return 0;
10 }
11
12 int main()
13 {
14 fun();
15
16 return 0;
17 }
/* 输出结果 */
the fun, 002.c, 7
我们定义宏名时尽量不要用这种方法,因为系统已经定义好了。
条件预处理
方式一:
#include <stdio.h>
#define ABC //开关打开,只需要这一个开关,在debug和release之间切换
int main()
{
#ifdef ABC
printf("%s \n",__FILE__);
#endif //预处理时是否屏蔽
printf("Hello World! \n");
return 0;
}
gcc -D宏名 //预处理之前通过编译器人为增加的一个宏名
//注:D后面直接跟宏名
e.g.
gcc -DABC == #define ABC
方式二:
003.c
#include <stdio.h>
int main()
{
#ifdef ABC
printf(" %s \n",__FILE__);
#endif //预处理时是否屏蔽
如果定义了"ABC",就打印文件名,即"003.c";
如果没有,就屏蔽。
printf("Hello World! \n");
return 0;
}
----------------------------------------------------
gcc -DABC -o build 003.c
./build
----------------------------------------------------
/* 输出 */
003.c
Hello World!
5.宏展开下的#、##
多用于内核或驱动中
# 字符串化
## 连接符号
使用场合:宏体中实现
e.g.
#include <stdio.h>
#define ABC(x) #x
#define DAY(x) myday##x
int main()
{
int myday1 = 10;
int myday2 = 20;
printf(ABC(ab\n)); //相当于 "ab\n"
printf("The day is %d \n",DAY(1)); //myday1
return 0;
}
-----------------------------------------------------------
/* 输出 */
ab
10
内核中应用例如:
6.关键字
关键字没有什么神秘,它其实就是一串字符串,只是这样的字符串被编译器赋予了一定的物理意义。
(1)sizeof
#include <stdio.h>
int main()
{
int a;
printf("The a is %lu \n",sizeof a); // %lu 无符号长整型 unsigned long
return 0;
}
--------------------------------------------------------------------------
/* 输出 */
The a is 4
sizeof(不是函数):编译器给我们查看内存空间容量的一个工具。
嵌入式开发中没有操作系统,裸板操作的话不能调用printf函数,但可以调用sizeof判断大小。
-----------------------------------------------------------------------------------------------------------------------------
C操作对象:资源(内存)(内存类型的资源:LCD缓存、LED灯)
C语言如何描述这些资源的属性?
我们需要考虑资源的大小,而数据类型帮我们解决资源大小限制的问题。
------------------------------------------------------------------------------------------------------------------------------
硬件操作的最小单位:bit(位) 1/0
软件操作的最小单位:8 bit(位) == 1 Byte(字节)
------------------------------------------------------------------------------------------------------------------------------
(2)char:一个字节
应用场景:硬件处理的最小单位
ASCII 码表 8bit
8 bit = 256 范围0~255
(3)int
大小:根据编译器来决定。
编译器最优的处理大小:系统一个周期所能接受的最大处理单位:int
32 bit 4 B int
16 bit 2 B int(比如单片机系统)
一个int可能是2个字节,也可能是4个字节。
2 Byte 65535
----------------------------------------------------------------------------------------------------
注:在C语言中,需要用特定的前缀来表示使用哪种进制。
0前缀表示八进制,0x/0X前缀表示十六进制,所以十进制的16表示成八进制是"020",表示成十六进制是"0x10"。
%d 以十进制显示数字
%o 以八进制显示数字
%x 以十六进制显示数字
把各进制前缀显示出来得加点修饰
八进制:%#o
十六进制:%#x
----------------------------------------------------------------------------------------------------------
(signed) int 右移
我们看到的数都是以二进制的形式在计算机下操作的,并且位运算符的操作对象是补码。
数的正负如何表示?
通常采用的方法是在二进制数的前面增加一位符号位。符号位为0表示这个数是正数,符号位为1表示这个数是负数。
这种形式的数称为原码。
正数:原码 = 反码 = 补码
负数:原码
反码:保留其符号位,将原码的数值位按位取反得到。
补码:反码 + 1。
e.g.
int a = 10;
0000 0000 0000 0000 0000 0000 0000 1010 ---- 原码
正数直接右移。
#include <stdio.h>
int main()
{
int a = 10;
a = a >> 2;
printf("a = %d \n");
return 0;
}
-----------------------------
/* 输出 */
a = 2
e.g.
int a = -10
1000 0000 0000 0000 0000 0000 0000 1010 ----- 原码
1111 1111 1111 1111 1111 1111 1111 0101 ---- 反码
1111 1111 1111 1111 1111 1111 1111 0110 ---- 补码
右移两位(在左边正数补0,负数补1)
1111 1111 1111 1111 1111 1111 1111 1101 ----右移两位后
接下来再算回去(取反,+1)或(-1,取反)
取反
1000 0000 0000 0000 0000 0000 0000 0010
+1
1000 0000 0000 0000 0000 0000 0000 0011
这个二进制表示:-3。
#include <stdio.h>
int main()
{
int a = -10;
a = a >> 2;
printf("a = %d \n");
return 0;
}
-----------------------------
/* 输出 */
a = -3
-----------------------------------------------------------------------------------------------------------------------------
unsigned、signed
内存空间的最高位是符号位还是数据
无符号:数据,显卡产生的数据。
有符号:数字,作为加减乘除使用的。
注:正数在内存中是以原码的形式储存,负数在内存中是以补码的形式存储。
如果是数据,尽量用unsigned int定义。
---------------------------------------------------------------------------------------------------------------------------------
(4)float、double
float:4个字节
double:8个字节
浮点型常量
1.0 1.1 double类型,占8个字节,非常耗费空间
1.0f float 类型,占4个字节
(5)void
void fun();函数不返回
7.自定义数据类型
自定义 = 基本元素的集合
(1)struct
元素之间的和
struct myabc{
unsigned int a;
unsigned int b; 告诉编译器这是我们自己定义的一块东西,即告知编译器我们具备了
myabc这样内存空间定义的能力。
unsigned int c;
unsigned int d;
};
stuct myabc mybuf;
结构体顺序是有要求的。
(2)union:共用体
共用起始地址的一段内存,技巧型代码。
union myabc{
char a;
int b;
}
(3)enum:常量的集合。
8.类型修饰符:对资源属性中位置的限定
(1)auto
可读可写中分配的一个区域,普通内存。
auto int a;
区域如果在{ }中,栈空间。
(2)register
限制变量定义在寄存器上的修饰符。
register int a;
内存(存储器) 寄存器
0x10 R0,R2
a放在寄存器上比放在内存中访问的效率要高很多,要快的多。
定义一些快速访问的变量,编译器会尽量安排CPU的寄存器去存放这个a,如果寄存器不足时,a还是放在存储器中。
&这个符号对register不起作用。
#include <stdio.h>
int main()
{
register int a;
a = 0x10; //十进制:16
printf("The a is %d \n",a);
return 0;
}
--------------------------------------
/* 输出 */
The a is 16
(3)static
静态
应用场景:
修饰3种数据:
(1)函数内的变量
int fun()
{
int a; ==> static int a;
}
(2)函数外部的变量:全局变量
int a; ==> static int a;
int fun()
{
}
(3)函数的修饰符
int fun(); ==> static int fun();
在内核源代码中会大量看到,在多文件工程中用的比较多。
(4)const
常量的定义(中看不中用)
只读的变量,在R/W区。
const int a = 100
-----------------------------------------------------------------------------------------------------
内存泄漏
-----------------------------------------------------------------------------------------------------
(5)volatile:告知编译器编译方法的关键字,不优化编译。
修饰变量的值的修改,不仅仅可以通过软件,也可以通过其他方式(硬件外部的用户)
int a = 100; 它的修改可能是外部来修改
while( a == 100);
mylcd();
-------------------------
[a]:a的地址
f1:LDR R0,[a]
f2:CMP R0,#100
f3:JMPEQ f1 ==> JMPEQ f2 /* 优化后 */
f4:mylcd()
----------------------------------------------------------------------------------------------------------------------------
汇编
[LDR]:从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的数据到通用寄存器,然后对数据进行处理。
LDR R0,[R1] //将存储器地址为R1的字数据读入寄存器R0
9.常用运算符
(1)* /
CPU
int a = b * 10; //CPU可能多个周期,甚至要利用软件的模拟方法去实现乘法
int a = b + 10; //CPU一个周期可以处理
(2)%
0 % 3 = 0 1% 3 = 1 2 % 3 = 2 3 % 3 = 0 4 % 3 = 1 ... ...
n % m = res [0,m-1]
1.取一个范围的数:
e.g. 给一个任意的数字,得到一个1到100以内的数字
(m % 100) + 1 => res
2.得到m进制的一个个位数
3.循环数据结构的下标
(3)逻辑运算符
真 假
返回结果就是 1 0
int a = -1;
if(a)
|| 、&&
A || B === B || A //不等
A && B
A || B
在C语言编译器中,它是这样考虑的,根据我们的翻译顺序,它首先看到A,假设A已经为真,那么B就不执行了,同样,在 (A && B)中,如果A已经为假,那么B就不执行了。它按先后顺序预判断,然后再决定是否执行B。
#include <stdio.h>
int main()
{
int a = 10;
int res;
res = (a==10) || printf("====\n"); //A 和 B 可以指向任何东西
printf("The res is %d\n");
return 0;
}
--------------------------------------------------------------------
/* 输出 */
The res is 1
--------------------------------------------------------------------
#include <stdio.h>
int main()
{
int a = 10;
int res;
res = (a!=10) || printf("====\n"); //A 和 B 可以指向任何东西
printf("The res is %d\n");
return 0;
}
---------------------------------------------------------------------
/* 输出 */
====
The res is 1
---------------------------------------------------------------------
!
对比位运算中取反
e.g.
int a = 0x0;
!a if(!a){}
~a 0xffff ffff
(4)位运算
<< 、>>
左移:乘法 ====> *2 二进制下的移位
如:m << 1;====> m * 2
m << n; ====> m * 2^n
4: 00100
8:01000
int a = b * 32;====> b << 5 (一个周期)
[数据、数字]
-1 * 2 -2:
8 bit
10000001 1000 0010
11111110 1111 1101
11111111 === -1 1111 1110
右移:除以2,m >> n,m/2^n
符号变量有关
int a; a >> n
unsigned int a; a >> n
-----------------------------------------------------------------------------------------------------
&、|、^
A & 0 = 0
&:屏蔽
e.g.
int a = 0x1234;
a & 0xff00;//屏蔽低8位
A & 1 = A
&:取出
&:清零器
|:
A | 0 = A
保留
A | 1 = 1
设置为高电平的方法
e.g.
设置一个资源的bit5为高电平,其他位不变
int a;
a = (a | (0x1 << 5)) =====> a | (0x1 << n)
清除第五位
int a;
a = a & ( ~(0x1 << 5)); ====> a = a & (~(0x1 << n));
-------------------------------------------------------------------------------------------------
^、~
^:
1 ^ 1 = 0 0 ^ 0 = 0
1 ^ 0 = 1
算法
int fun()
{
int a = 20;
int b = 30;
int c; 方法一
c = a;
a = b;
b = c;
-----------------
a = a ^ b; 方法二
b = a ^ b;
a = a ^ b;
a = 30; b = 20;
}
~:
0xf0 ~ -> 0xffff ff0f