当前位置: 首页 > news >正文

嵌入式c学习九

c语言函数:c语言基本单位是函数,c语言是一门面向过程的编程语言,采用自顶向下设计思想

库函数:标准库,第三方库

函数定义:declaration-specifiers   declarator  declaration-list(opt) compound-statement

 declarator:标识符,定义函数时需要申明一个标识符用于表示函数名称

declaration-specifiers:函数说明符,函数的返回类型可以是void或者是一个对象类型,而不是数组类型

declaration-list(opt):声明列表(可选),如果函数包含一个参数列表,则列表中每个参数都应该有一个标识符,可以没有参数列表--》(void)

compound-statement:复合语句,{}

1、函数有参数列表,应该在函数名称的()中写清楚每个参数的类型,以及每个参数的名称

函数类型        函数名称(参数1类型  参数1名称, 参数2类型  参数二名称......)

{

}

2、函数没有参数列表,若没有参数列表需要在函数名称的()中填写void

函数类型        函数名称(void)

{

}

void:在c语言标准中是一个关键字,含义具有空的意思,在参数列表中出现,表示函数没有参数

同样如果void是函数类型,则表示函数没有返回值

函数类型:函数类型就是函数返回值类型,可以是void或者是对象类型(基本数据类型 int  char  float....+复杂数据类型+指针),但是函数的返回类型不允许是数组

如果函数有返回值类型,则函数内部的需要返回数据的类型,必须要好函数类型一致,需要在函数内部使用return语句实现

int 函数名称1(void)
{
	return 33.3; //错误,实际返回类型和定义函数时的声明类型不一致 
}

void 函数名称2(void)
{
	return 10; //错误,void作为函数类型,则函数没有返回值 
}

int[10] 函数名称3(void)
{
	int buf[10] = {0};
	return buf; //错误,函数返回类型不允许是数组,但是可以选择传递地址 
}

int * 函数名称4(void)
{
	int buf[10] = {0};
	return buf; //可以,函数返回类型不允许是数组,但是可以选择传递地址 
}

指针函数:如果函数类型是一个指针地址,则表示函数可以返回一个地址,这种函数成为指针函数

不能在已经定义的函数中定义新的函数,可以调用新函数;低耦合,高内聚

耦合度:两个或两个以上模块之间关联的紧密程度

内聚度:一个模块内部各部分的紧密程度

函数调用:先定义,后使用原则,由于c语言中程序是以函数为单位,且程序入口为main()函数

#include <stdio.h>

//声明函数,声明函数只需要说明函数的原型即可 
int suma(void);

//先定义函数,在使用函数 
int add(void)
{
	return 5;
}

int main()
{
	//函数调用 
	int a = add(); 
	suma();
  
  return 0; 
  
}

//后先声明函数,后定义函数 
int suma(void)  
{
	printf("a\n");
}

练习:设计函数,传递两个整数,输出最大值

#include <stdio.h>

void maxnum(int a, int b)
{
	printf("maxnum is :%d\n", (a > b) ? a:b);
} 

int main()
{
	maxnum(3,5);

  return 0; 
}

函数的数据传递:需要在设计函数是说明清楚,函数需要传递的参数类型以及参数名称,都是在定义函数的时候,通过参数的参数列表传递,函数的参数一般是形参

在定义函数时,函数列表中的形参是不占内存的,当函数被调用后,函数形参才会得到对应的内存,并且函数的形参知会在函数内部生效,函数调用完后形参的内存会被系统自动释放

单向传递:只是把实参的值传递给函数作为参数,在函数内部对数值进行修改不会影响函数外部实参。

#include <stdio.h>

void fun(int a, int b)
{
	a = 30;    //函数内部形参修改,不会改变实参的值 
	b = 40;
} 

int main()
{
	int a = 10;
	int b = 20;
	printf("a:%d,b:%d\n",a ,b);
	
	fun(a,b);   //传递实参的值 
	printf("a:%d,b:%d\n",a ,b);

  return 0; 
}

双向传递:可以直接把实参的地址作为参数传递给函数内部

#include <stdio.h>

void fun(int *a, int *b)
{
	*a = 30;    //间接访问地址下面存储的数据,并进行修改 
	*b = 40;
} 

int main()
{
	int a = 10;
	int b = 20;
	printf("修改前a:%d,b:%d\n",a ,b);
	
	fun(&a,&b);   //传递实参的地址 
	printf("修改后a:%d,b:%d\n",a ,b);

  return 0; 
}

生命周期:指变量的生命周期,即变量从得到内存到释放内存的时间就是变量的生命周期,程序中变量按照存储单元的属性分类,可以分为变量和常量,也可以按照生命周期进行划分,可以分为全局变量和局部变量。

局部变量:在函数内部定义的变量或者在某个符合语句定义的变量都被称为局部变量,当函数结束时,局部变量内存会被释放

全局变量:在所以函数的外部(在所有复合语句的外部)定义的变量是全局变量,程序结束时内存才被释放

作用范围:变量的作用域,即变量的有效使用范围,全局变量的作用域是整个程序所以程序中任何一个函数都有访问权限;局部变量而言,作用域只针对当前局部变量的符号语句有效。

#include <stdio.h>

int a ; 		//全局变量,作用域是针对整个程序 

void func()
{
	int b;		//局部变量,作用域是针对当前函数的复合语句 
}

int main()
{
	int c;		//局部变量,作用域是针对当前函数的复合语句 
	
  return 0; 
}

当全局变量的名称和局部变量名称相同时,则应该遵循就近原则,即优先使用同一个作用域的变量,如果该作用域中没有该变量则可以扩大作用域

数组的传递:实际上是把数组的地址传过去

#include <stdio.h>

//函数的参数类型就是数组 
void func1(int buf1[5])	
{
	
}

//函数的参数类型是指针,可以接收地址 
void func2(int *buf2)
{
	
} 

int main()
{
	int buf[5];
	
	//调用函数,数组名就表示函数的首地址 
	func1(buf);
	func2(buf);
	
    return 0; 
}

数组长度的传递:把数组长度当作参数传递

#include <stdio.h>

void func(int *buf2, int size)
{
	for(int i=0; i<size; i++){
		printf("buf[%d]=%d\n", i, buf2[i]);
	}	
} 

int main()
{
	int buf[5] = {1,2,3,4,5};
	
	//sizeof(buf)/sizeof(buf[0])得到数组的尺寸,作为参数传递 
	func(buf, sizeof(buf)/sizeof(buf[0]));

    return 0; 
}

函数返回局部变量的地址:

函数不能直接返回局部变量的地址,如下程序,数组buf是局部变量,当函数fun结束后,buf对应的内存也会消失,此时无法返回对应地址

#include <stdio.h>

char *fun(void)
{	
	//局部变量,当函数终止时数组buf地址对应的内存被释放 
	char buf[10] = "asdf";
	printf("buf首元素地址%p\n", buf);

	//把数组的首元素地址返回(此数组是局部变量)
	return buf;
} 

int main()
{	
	char *p = fun();
	
	printf("fun函数返回的地址%p\n", p);
	
    return 0; 
}

解决方案:①把函数内部的数组变量定义为全局变量②可以选择把函数内部的局部变量的生命周期延长

如果在函数内部定义一个局部变量,则会从内存分区中的栈空间中分配一块内存给局部变量,栈空间是由系统自动管理,所以当函数调用结束时系统会自动释放该局部变量的内存

若函数中定义的局部变量使用static关键字进行修饰,则系统会从全局数据区分配内存空间给该局部变量,全局数据区生命周期是跟随程序的,不会因为函数结束而释放

static:可以修饰局部变量,也可以修饰函数,修饰函数时表示限制函数的作用域为文件内部有效

#include <stdio.h>

char *fun(void)
{	
	//静态局部变量,当函数终止时数组buf内存不会被释放,程序结束才被释放 
	static char buf[10] = "asdf";
	printf("buf首元素地址%p\n", buf);
	//把数组的首元素地址返回(此数组是静态局部变量)
	return buf;
} 

int main()
{	
	char *p = fun();
	
	printf("fun函数返回的地址%p\n", p);
	printf("p:%s\n", p); 
	
    return 0; 
}

内存分布:采用32位的linux系统,则每个运行的程序都会得到4G大小的内存空间,只不过每个程序得到的4G大小都是虚拟内存,而物理内存只有4G,物料内存是真实存在的,虚拟内存是通过映射得到的。

32位系统表示,可寻址的内存空间是2^32个地址,每个地址对应1字节的存储空间,共计2^32 = 4294967296字节(Byte),1KB=1024B(字节),1MB=1024KB,1GB=1024MB

4294967296B/1024=4194304KB

4194304KB/1024=4096MB

4096MB/1024=4GB

2^10=1024;2^32=2^10 * 2^10 * 2^10 * 2^2 = 4 * 2^10 * 2^10 * 2^10 (KB--MB--GB)

虚拟内存是由物料内存映射而来,都需要计算机中的MMU——内存管理单元

             物理内存:

 

1、保留区:

        保留区又称为不可访问区域,用户是没有权限访问的,对于Linux系统而言,保留区地址范围是0x0000_0000~0x0804_8000,此保留区的大小是128M(0x0804_8000转换为10进制为8407040个地址,即有8407040字节内存(B)==8210KB==8M),一般用户定义的指针变量在初始化的时候就可以指向这快空间,此空间任何程序都没有访问权限,所以可以确保指针不会被误用,可以防止野指针出现,宏定义NULL就是指向0x0000_0000

int *p = NULL;————定义指针变量,并初始化为NULL--指向保留区

2、代码段:

嵌入式微处理器体系结构

冯诺依曼结构:程序指令存储器和数据指令存储器合并在一起的存储器结构

哈佛结构:将程序指令存储器和数据指令存储器分开的存储器结构,提高了执行速度

程序是由数据及指令组成,代码段存储的是对程序编译后生成的二进指令,代码段分为.text段和.init段

.inti段:用于存储系统初始化的指令,属性是只读的
.text段:用于存储用户程序生成的指令,属性是制只读的

在程序运行之前,代码段的内存空间就已经被内核计算完成,在程序运行之后代码段的内容就不应该再被修改。

数据段:程序是由数据和指令组成,根据数据的生命周期和数据类型不同,一般把数据存储在两个部分,一个部分是栈空间,一个部分是数据段

数据根据数据类型(常量/变量)以及根据数据是否被初始化(已初始化/未初始化)把数据存放在三个不同位置:.rodata段,.bss段,.data段

.rodata段:称为只读常量区,程序中的常量都是存储在此区域。其属性是只读的,程序结束内存会被释放

.data段:存储程序中已经被初始化的全局变量,和已经被初始化的静态局部变量,初始化不能为0!

.bss段:用于存储程序中未被初始化的全局变量,和未被初始化的静态局部变量,以及初始化未0的全局变量与静态局部变量

4、堆空间:用户可以随意支配的内存,需要提前向内核申请,可以通过库函数malloc()、calloc()申请堆内存,堆空间需要用户手动申请和手动释放,释放通过库函数free()释放,堆内存属于匿名内存只能通过指针访问,内存地址分配向上递增

malloc:void *malloc(size_t size);可以申请size个字节的内存空间,该内存是未被初始化的,申请成功返回堆内存的首地址,申请失败返回NULL,需要包含头文件stdlib.h

calloc:void *calloc(size_t n, size_t size );可以申请n个size字节大小内存,此内存还是连续的,且申请的内存自动被初始化为0,申请成功返回堆内存的首地址,申请失败返回NULL,用户应该进行错误处理!

free:void free(void *ptr);free()函数可以释放参数ptr指向的内存,ptr指向的内存必须是通过malloc或者calloc申请的,且同一块内存不能释放多次。

栈空间是由用户进行支配,所以用户申请成功后,使用完需要及时释放栈空间,并且必须手动释放,且只能释放一次,如果不释放会造成内存泄露。当把申请的堆内存释放后,则应该同样把指向堆内存首地址的指针指向NULL

#include <stdio.h>
#include <stdlib.h> 
#include <string.h>


int main()
{
	//利用malloc申请堆节内存,并进行初始化和错误处理 
		
	int *p = NULL;				//定义指针变量P并初始化,NULL指向保留区 
	
	p = (int *)malloc(20); 		//malloc申请20字节堆内存
	
	if(NULL == p)      			//错误处理 
	{
		perror("malloc failed");//perror函数可以输出格式化字符串,并且可以输出错误原因 
		
		return -1;              //程序异常退出 
	}	
	
	bzero(p, 20); 				//申请堆内存成功进行初始化 
		
	free(p);					//使用完成后调用free函数进行释放 
	
	p = NULL;					//指针重新指向NULL保留区,防止内存泄露 
	

	//利用calloc申请堆内存
	int *q = NULL;	
	
	q = (int *)calloc(5,4);		//利用calloc申请5个4字节大小的堆内存,calloc申请的内存被系统自动初始化为0 
	
	if(NULL == q){
		perror("calloc failed");
		return -1;
	} 
	 
	free(q); 
	q = NULL; 
	
    return 0; 
}

5、栈空间:

栈空间主要用于存储程序的命令行参数,局部变量,函数的参数值,函数的返回地址,当函数被调用期间,内核会分配对应大小的栈空间给函数使用,当函数调用完成后栈空间就会从内存释放。

栈空间的内存数据是随机值,用户在得到栈空间后需要进行初始化,防止变量中值的不确定

栈空间内存地址分配是向下递增,栈空间遵循--先进后出原则。

linux系统中空间容量是有限的,如果超出容量会发生栈溢出,导致段错误

#include <stdio.h>

int main()
{
	char a[100*1024*1024]; 		//局部变量从栈空间分配,栈空间大小一般是8M,所以此程序运行时会发生段错误 

    return 0; 
}

linux系统可以使用ulimit指令修改栈空间大小,修改是一次性的,不是永久有效的。

内存存储位置确认:

#include <stdio.h>


int c;                     //未初始化的全局变量,存储在数据段中的.bess段 

int d = 0; 				   //初始化为0的全局变量,存储在数据段中的,bess段

int a = 1;				   //初始化不为0的全局变量,存储在数据段中的.data段 

int main()
{
	int *p = NULL;         //局部变量由栈空间分配内存 
	
	static char a;		   //初始化不为0的静态局部变量,存储在数据段的.data段 
	
	char b = 'H';		   //局部变量由栈空间分配内存 
	
	static int f = 20; 	   //数据段中的.data段,已被初始化的静态局部变量 
	
		
    return 0; 
}

相关文章:

  • 机械臂如何稳稳上桌?Mujoco场景修改实操
  • 代购系统:架构设计、功能实现与用户界面优化
  • Nginx RTMP HLS模块分析 (ngx_rtmp_hls_module.c)
  • 矿山自动化监测解决方案
  • python中的继承
  • 基于Python的Manim面向对象(2)
  • HTML应用指南:利用POST请求获取全国小鹏汽车的充电桩位置信息
  • 紧急需求救星:1 小时用 AI 生成电商订单模块
  • -PHP 应用SQL 盲注布尔回显延时判断报错处理增删改查方式
  • 进军场景智能体,云迹机器人又快了一步
  • Unity Shader编程】之FallBack
  • PGP实现简单加密教程
  • Go常见问题与回答
  • NLP高频面试题(二十)——flash attention原理
  • 蓝桥杯备考:真题之飞机降落(暴搜+小贪心)
  • 2025年具有AI招聘管理系统选型及攻略分享
  • 新手SEO优化实战快速入门
  • AG7220替代方案|ASL6328芯片设计|HDMI2.0 Retimer中继器方案
  • 6. 理解中间件与认证中间件
  • 蓝桥杯C++基础算法-多重背包(优化)
  • 网站建设调查表/百度竞价点击神器奔奔
  • 网站 服务器 域名/优化方案电子版
  • 交互 网站/小红书软文推广
  • 公司网站策划方案/南宁seo
  • 网站建设 asp 武汉/网站提交
  • 网站建设报价分析/网络推广一个月的收入