嵌入式C语言

文章介绍了嵌入式C语言中的预处理概念,包括宏定义、条件编译和预定义宏。接着讨论了C语言的关键字,如数据类型、逻辑结构和运算符。此外,详细阐述了内存空间的使用,特别是指针、数组和结构体的处理,以及内存分布图。文章还提到了函数的使用,包括参数传递和返回值的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

嵌入式C语言

1 C语言预处理

  • 包含头文件

    #include
    
  • 宏(单纯替换,不进行语法检查,最好加上括号)

    #define	宏名 宏体
    
  • 条件预处理(满足条件才执行)

    #ifdef	ABC
    	printf("%s %s %d", __FUNCTION__, __FILE__, __LINE__);
    #endif
    //gcc 编译时可以加上-D动态注册宏定义
    
  • 预定义宏

    __FUNCTION__ : 函数名
    __LINE__ : 行号
    __FILE__ : 文件名
    printf("%s %s %d", __FUNCTION__, __FILE__, __LINE__);
    
  • 字符串化 #

  • 连接符号 ##

    #define ABC(x) 	#x		//替换成"x"
    #define ABC(x)	abc##x	//替换成abcx
    

2 关键字及运算符

2.1 关键字:编译器预先定义了一定意义的字符串

  • 数据类型

    • char
    • int
    • long、short
    • unsigned、signed
    • float、double
    • void
  • 自动义类型

    • struct

    • union

    • enum(常量列表)

    • typedef(xxx_t)

      typedef struct Student{
          int id;
          char name[100];
          char sex;
      }* PST, ST;				// PST 等价于 struct Student *, ST等价于struct Student
      
  • 逻辑结构

    • if、else
    • switch、case、default
    • do、while、for
    • continue、break、goto(函数内部进行跳转)
  • 杂项

    • return
    • sizeof(其实也是运算符)
  • 类型修饰符(资源属性中位置的限定)

    • auto

      自动存储期,默认属性,自动变量,可读可写,如果再{ },栈空间

    • register

      自动存储期,请求变量是寄存器的变量(不一定生效),访问和处理的速度更加快。

    • static

      静态存储期,静态外部、静态内部、静态无链接。

    • const

      常量的定义,只读的变量(实际可以根据某些方法修改,比如指针越界访问)。

    • extern

      外部申明

    • volatile

      易变的,告知编译器不要优化。

  • 运算符

    • 算术操作运算符

      • +、-

      • *、/、%

        /*
        	0 % 3 = 0 1 % 3 = 1 2 % 3 = 2 3 % 3 = 0 4 % 3 = 1
        	n % m = res res的结果在[0, m-1]之间
        	利用这个特性
        	1.取一个范围的数:
        		eg.给一个任意的数字,得到一个1到100以内的数字?
        		(m % 100) + 1 ===>res;
        	2.得到M进制的一个个位数
        		eg.转化成16进制
        	3.循环数据结构的下标
        		eg.环形缓冲区
        		char buf[LEN];
        		写数据:
        			buf[w] = val;
        			w = (w + 1) % LEN;
        		读数据:
        			val = buf[R];
        			R = (R + 1) % LEN;
                怎么判断空:R == W;
                怎么判断满:(W + 1) % LEN == R;
        		
        */
        
        
    • 逻辑运算

      • ||、&&(短路特性)

      • >>=<<=
        
      • ? :

    • 位运算

      • <<、>>(编写代码过程尽量多用移位少用乘除)

      • &、|

        /*
        A & 0 -----> 0
        &:屏蔽
          int a = 0x1234;
          a & 0xff00;屏蔽低8bit,取出高8bit
        A & 1 -----> A
        &:取出
        &:清零器
        
        |:
        A | 0 === A
        保留
        A | 1 === 1
        设置为高电平的方法,设置set
        
        设置一个资源的bit5为高电平,其它位不变
        int a;
        a = (a | (0x1<<5));   =====>a |= (0x1<<n)
        
        清除第五位
        
        int a;
        a = a & ~(0x1<<5);    ====> a &= (~(0x1<<n))
        */
        
      • ~、^

        //^ 不引入新变量交换值
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        
    • 赋值运算

      • =
      • +=、-=、&=
    • 内存访问符号

      • ()
      • []
      • {}
      • ->、.
      • &、*

3 C语言内存空间的使用

3.1 指针

内存类型资源地址、门牌号的代名词

  • 指针+修饰符

    • const

      const int *p;//常量指针,指向常量的指针。即P指向的内存可以变,P指向的数值内容不可变
      int const *p;//同上
      int* const p;//指针常量,本质是一个常量,而用指针修饰它。即P指向的内容不可变,但是P内存位置的数值可以变,硬件常用
      const int* const p;//指向常量的常量指针。都不可变,寄存器
      
    • volatile

      //防止优化指向内存地址,只跟硬件有关
      volatile char *p;
      
    • typedef

  • 指针+运算符

    • ++、–、+、-

      实际加减一个单位,自增(减)会更新该指针

    • []

    • 逻辑操作符

  • 多级指针

3.2 数组

  • 数组的定义

    //一维数组名是一个常量符号(标签,指针常量),一定不要放到=的左边,自然也不能用自增(减)
    //编译器不会帮你检查是否越界,自己一定要注意。
    int a[100];
    a[-100];//可以,相当于首地址往下偏移100个单位
    
  • 数组空间的初始化

    /*
    空间的赋值
        按照标签逐一处理
        数组空间的初始化 和 变量的初始化 本质不同,尤其在嵌入式的裸机开发中,空间的初始化往往需要库函数的辅助
        int a[10] = {1,2,3};
        实际上,编译器帮你做了=======>a[0] = 1;a[1] = 2;a[2] = 3;a[3] = 0;...
    
    char
        1.char buf[10]={'a','b','c'};
        定义普通变量没问题
        如果要初始化字符串"abc",结尾一定要加上'\0'========>{'a','b','c','\0'};
        2.char buf[] = {"abc"};字符串最合理的写法,编译器看到""自动帮你加'\0'
        3.char buf[] = "abc";更简单的写法
            区分char *p = "abc"; 和 char buf[] = "abc";
            p是直接指向常量区的字符串,但是buf是拷贝了一个常量区的副本通过标签逐一处理拷贝至数组空间
    
        编译器只提供第一次初始化赋值,后面如果想变就只能逐一处理,为解决需求引入
            char *strcpy(char *dest, const char *src);不安全,有内存泄露风险
            char *strncpy(char *dest, const char *src, size_t n);
    对于非字符空间
        数据采集
    
        char buf[10];-------->string
        unsigned char buf[10];--------->data
        拷贝数据时,没有结束标志,只能定义个数
        拷贝三要素:1.src
                  2.dest
                  3.个数
        void *memcpy(void *dest, const void *src, size_t n);
        unsigned char buf[10];
        unsigned char sensor_buf[100];
        memcpy(buf, sensor_buf, 10*sizeof(unsigned char));
    
    */	
    
  • 指针与数组

    char *a[100];	sizeof(a) = 100*4;
    
  • 多维数组

    int b[5][6];
    int **p = b;错误,二维数组和二维指针没有任何关系。
    解析:
    	b+1,一次移动6个int单位的数据
    	p+1,一次移动4个字节
    	应将p修改为======>int (*p)[6]
    	这样p每次移动也是6个单位的int
    	由此也可知道,二维数组的列很重要,初始化时,行可以空着,列必须有
    int b[2][3][4];
    int (*p)[3][4] = b;
    

3.3 结构体

  • 字节对齐

    效率,希望牺牲一点空间换取时间的效率
    最终结构体的大小一定是4(32bit系统指针是4字节,除非遇到double,转成8的倍数)的倍数或者8(64bit系统指针是8字节)的倍数
    结构体里成员变量的顺序不一致,也会影响到它的大小
    eg.32位系统
    struct abc{
    	char a;
    	short e;
    	int b;
    };
    4字节对齐,a填充三字节,余下的字节够e占两字节,指针可以一次读两个。b正好4字节,4+4=8.
    
    struct abc2{
    	char a;
    	int b;
    	short e;
    };
    4字节对齐,a占一字节,余下的三字节不够b存放,填充三字节,b占四字节,e占两字节,填充两字节(为后面的变量考虑)。4+4+4=12
    
    //另一种对齐方式
      1. 结构体变量中成员的偏移量必须是成员大小的整数倍 
      2. 结构体的最终大小必须是结构体最大简单类型的整数倍
      
    struct xx {
        long long _x1;
        char _x2;
        int _x3;
        char _x4[2];
        static int _x5;
    };
    x1的偏移量是0,长度是8,符合;
    x2的偏移量是8,长度是1,符合;
    x3的偏移量是9,长度是4,不符合,需要在x2之后填充3字节使得x3的偏移量达到12;
    x4的偏移量是16,长度是2,符合;
    此时总长度为(8)+(1+3)+(4)+(2)=18,而最大简单类型为long long长度为8,因此需要在x4之后再填充6字节,使得总长度达到24可被8整除
    

3.4 内存分布图

  • 栈空间

    内存的属性:1.大小	2.在哪里
    
    内存分布图:
    
    0xfffffff
    	内核空间		应用程序不许访问
    -----------------------3G
    	运行时的栈空间		局部变量
    -----------------------
    	运行时的堆空间	 malloc
    -----------------------
    	全局的数据空间		static		RW	data(初始化的) bss(未初始化的)
    	只读数据段	  "hello world"	 	 R	text
    	代码段			code			  R	 text
    -----------------------
    0x0:
    
    栈空间运行时,函数内部使用的变量,生存周期是函数内,一旦返回就释放
    堆空间运行时,可以自由自我管理的分配和释放的空间,生存周期由程序员来决定。
    只读空间认为是静态空间,程序编译时就确定,整个程序结束时释放内存,生存周期最长
    
  • 堆空间

    配对使用
    分配多少,以什么方式读取,程序员决定
    char *p;
    p = (char *)malloc(sizeof(int)*100);
    free(p);
    
  • 只读空间

3.5 函数的使用

  • 函数概述

    函数三要素:
    		1.函数名(地址)
    		2.输入参数
    		3.返回值
    在定义函数时,必须将3要素告知编译器
    
    如何用指针保存函数?
    int (*p) (int, int, char);有了三要素p读内存的方法就知道了
    
  • 输入参数

    • 值传递

      单纯的值拷贝,上层调用者保护自己空间值不被修改的能力

    • 地址传递

      上层调用者让下层子函数修改自己空间值的方式

      1.修改某个值	int * short * long * int**(指针)
      2.空间传递
      	2.1子函数看看空间里的情况	const *
      	2.2子函数反向修改上层空间里的内容	char*(字符串)	void*(数据)
      
    • 连续空间的传递(地址传递)

      从头到尾,逐一遍历
      数组
      	形参:
      	int p[10] 只是为了告诉你这个数组的大小,但实际传递的还是指针。
      	void fun(int p[10]) =======> void fun(int *p)
      结构体
      	使用地址传递,不仅节省空间,使用还方便。
      为了避免对形参 char *p 的歧义
          const char *p 只读
          char *p 可修改
      空间二要素:空间首地址、结束标志
      字符空间
      	结束标志:内存里面存放了0x00(1B)
      	字符空间的标识符: char *		
      	字符空间举例:
      		int strcpy(char *dest, const char *src);
      非字符空间
      	非字符空间0x00,不能当成结束标志,用数量(B)作为结束标志
      	数据空间(连续的)的标识符: void *
      	如果只是单纯修改某个值:那就用:具体类型(int) *
      	非字符空间举例:(首地址,数量缺一不可)
      		void fun(void *buf, int len)
      		{
      			unsigned chr *tmp = (unsigned char *)buf;//void *只是形参化,最终的操作一定要把他转化成具体类型
      			int i;
      			for(i = 0; i < len; i++)
      			{
      				tmp[i] = ; a = tmp[i]//+++++++++++------------
      			}
      		}
      
  • 返回值

    • 基本语法

      返回类型 函数名(参数列表)
      {
      	return
      }
      
      调用者,用值去接收
      	a = fun();
      被调者
      	int fun()
      	{
      		return num;
      	}
      	
      都是拷贝的概念
      
      返回类型
      	基本数据类型
      	指针类型(空间)
      	不可以返回数组
      
    • 返回基本数据类型

      需要两个返回值时
      	int * fun(int *p);
      进行指针传递的时候要特别注意,自己的目的是否是值传递
      	void fun2(int *p);
      	int main()
      	{
      		int *p;
      		fun2(p);//这边是典型的值拷贝,传递了指针p的值
      	}
      	如果希望修改指针p所指向的地址空间,那么传递实参时,应该对p取地址,函数定义形参也该改为二维指针
      	即:void dun2(int **p);
      		fun2(&p);
      
    • 返回连续空间类型

      指针作为空间返回的唯一数据类型
      
      int *fun();
      地址:指向的合法性
      作为函数的设计者,必须保证函数返回的地址所指向的空间是合法。【不是局部变量】
      eg.
      	char *fun(void)
      	{
      		char buf[] = "hello,world!";
      		return buf;
      	}
      	int main()
      	{
      		char *p;
      		p = fun();//错误,fun被调用后,资源被释放,p指向的就是无效地址
      	}
      	
      使用者:
      int *fun();
      int *p = fun();
      
      函数内部实现
      	1.静态区
      	2.只读区(不太常用)
      	3.堆区
      	eg.
      	char *fun(void)
      	{
      		//static char buf[] = "hello world";
      		char *s = (char *)malloc(100);//一定记得释放
      		strcpy(s, "helloc world");
      		return s;
      	}
      	int main()
      	{
      		char *p;
      		p = fun();
      		pritnf("%s", p);
      		free(p);
      		return 0;
      	}
      
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值