C语言加强--韦老师公开课

2023-05-16

目录

1.变量与指针

摘要:普通变量、指针变量所占的内存空间大小,变量在内存中的分配方式(首地址要求、长度、分配长度与实际使用长度区别、需要填充字节数、全局与局部变量在内存中的位置);

1.1例程

全局与局部分配内部时的(位置)区别

1.2我的测试

1.3提问

2.变量赋值

2.1 简单的变量赋值

2.2 sizeof和关键字

2.2.1 sizeof

2.2.2 关键字

volatile — 作用/使用场景

const

static

extern

摘要:外部(文件)变量的几种引用方式&建议方式、个人总结笔记

我的笔记:

2.3 struct引用结构体变量&结构体的赋值

2.3.1 引入结构体

2.3.2 问题

3.结构体数据类型的元素内存分配问题

总结

4.探究数组成员对于结构体的意义

2.3.3 结构体赋值

2.4 类型转换

2.5 指针&函数指针

2.5.1 变量赋值的解析

2.5.2 通过指针赋值

2.5.3 结构体指针

总结:

结构体变量&结构体指针作为函数参数

2.5.4 函数指针

摘要:函数指针的本质,函数指针变量的定义方式、强制类型转换、使用方法,公司使用案例(如何使程序支持(并自动识别)多款(同类型,可相互替代)设备)

3.链表操作

3.1 基础链表

3.2 链表插入

3.3 链表删除

4.ARM架构

5.几条汇编指令(堆、栈)

6. 几个核心问题

6.1 有值的全局变量的初始化

6.2 全局变量的在内存/flash掉电特点:

6.3 初始值为0、没有初始化的全局变量,怎么初始化?

6.4 (完成前面之后)才去调用main函数

6.5 局部变量在哪?

6.6 局部变量的初始化

6.7 栈的作用


 韦老师的整理笔记:

嵌入式C语言(上),嵌入式C语言(中),嵌入式C语言(下)


主题如下:

  • 变量与指针

  • 链表操作

  • ARM架构简述

  • 几条汇编指令

  • 结合汇编掌握:全局变量、局部变量、变量赋值、地址操作、栈等深层次的知识

git下载: 本教程所有资料放在如下GIT仓库里:


git clone https://e.coding.net/weidongshan/rtos_training/c_improve.git:
  

1.变量与指针

摘要:普通变量、指针变量所占的内存空间大小,变量在内存中的分配方式(首地址要求、长度、分配长度与实际使用长度区别、需要填充字节数、全局与局部变量在内存中的位置);

2个口诀: 变量变量,能变,就是能读能写,必定在内存里 指针指针,保存的是地址,32位处理器中地址都是32位的,无论是什么类型的指针变量,都是4字节

1.1例程


...
volatile int a;
volatile char c;
volatile char buf[100];
volatile int *p;
volatile char *p2;
​
int main(void)
{
    a=1;
    C='A';
    buf[99]='B';
    ...
}  

源码在开发板上编译(?)运行,生成的RTOSdemo.map中关于int 型和 char 型(全局)变量的内存占用大小如下:

总结:

  1. .char/short/int/long/double/struct xx等所有数据类型的指针变量的内存大小,在32位系统下是4字节,在64位系统下都是8字节。

全局与局部分配内部时的(位置)区别

注1:视频中演示的.map文件的主要功能,是标志程序、数据、IO,在编译时和空间地址之间的映射关系。因此,该文件不能显示临时生成的局部变量的内存地址,但可以显示全局变量的内存地址。(即全局变量在源码的编译后的二进制代码时,已分配内存空间,而局部变量没有,局部变量需要在执行函数时在栈中临时分配。)

1.2我的测试


#include <stdio.h>
​
struct StudentInfo{
    char *name;
    int age;
    int high;
};
struct StudentInfo2{
    char num;
    int age;
    int high;
};
​
volatile int val;
volatile char c;
volatile char buf[100];
volatile struct StudentInfo g_st;
volatile struct StudentInfo2 g_st2;
​
volatile int *p_i;  //全局变量,volatile防止编译器因为变量未使用而自动优化去掉;
volatile char *p_c;
volatile short *p_s;
volatile double *p_d;
volatile struct StudentInfo *p_st;
volatile struct StudentInfo2 *p_st2;
​
int main(void)
{
    printf("sizeof(char)=%d, sizeof(short)=%d, sizeof(int)=%d, sizeof(long)=%d\n", 
        sizeof(char), sizeof(short), sizeof(int), sizeof(long));
    printf("sizeof(float)=%d, sizeof(double)=%d, sizeof(buf)=%d\n", 
        sizeof(float), sizeof(double), sizeof(buf));
    printf("sizeof(struct StudentInfo)=%d, sizeof(struct StudentInfo2)=%d\n", 
        sizeof(struct StudentInfo), sizeof(struct StudentInfo2));
    printf("sizeof(struct StudentInfo)=%d, sizeof(struct StudentInfo2)=%d\n", 
        sizeof(g_st), sizeof(g_st2));
    
    printf("sizeof(int *)=%d \n", sizeof(p_i));  //sizeof(p)=4;
    printf("sizeof(char *)=%d \n", sizeof(p_c));
    printf("sizeof(short *)=%d \n", sizeof(p_s));
    printf("sizeof(long *)=%d \n", sizeof(long));
    printf("sizeof(double *)=%d \n", sizeof(p_d));
    printf("sizeof(struct StudentInfo *)=%d \n", sizeof(p_st));
    printf("sizeof(struct StudentInfo2 *)=%d \n", sizeof(p_st2));
​
    return 0;
}
​
/* Microsoft Visual C++6.0测试结果,32位:
sizeof(char)=1, sizeof(short)=2, sizeof(int)=4, sizeof(long)=4
sizeof(float)=4, sizeof(double)=8, sizeof(buf)=100
sizeof(struct StudentInfo)=12, sizeof(struct StudentInfo2)=12
sizeof(struct StudentInfo)=12, sizeof(struct StudentInfo2)=12
sizeof(int *)=4
sizeof(char *)=4
sizeof(short *)=4
sizeof(long *)=4
sizeof(double *)=4
sizeof(struct StudentInfo *)=4
sizeof(struct StudentInfo2 *)=4
Press any key to continue
*/
​
/*64位Ubuntu执行结果:
sizeof(char)=1, sizeof(short)=2, sizeof(int)=4, sizeof(long)=8
sizeof(float)=4, sizeof(double)=8, sizeof(buf)=100
sizeof(struct StudentInfo)=16, sizeof(struct StudentInfo2)=12
sizeof(struct StudentInfo)=16, sizeof(struct StudentInfo2)=12
sizeof(int *)=8 
sizeof(char *)=8 
sizeof(short *)=8 
sizeof(long *)=8 
sizeof(double *)=8 
sizeof(struct StudentInfo *)=8 
sizeof(struct StudentInfo2 *)=8 
*/
​
证明:
1.char/short/int/long/double/struct xx等所有变量类型指针,在32位系统下是4字节,在64位系统下都是8字节。
2.64位与32位linux c开发时默认字节对齐方式分别为8和4。
问题:对于结构体struct StudentInfo2,有时候会产生内存大小为16的结果,暂不知道原因。  

1.3提问

提问:老师是不是只读的变量放在flash中?所以只读的加个const就节省ram?

答:应该是只读的常量(?),保存在flash中,但有时可能会被优化而放到内存中。

提问:老师,我要测试指针p占得内存使用sizeof(p)还是sizeof(*p)

2.变量赋值

2.1 简单的变量赋值


int a;
a = 1;
a = 'A';
  

2.2 sizeof和关键字

关键:关键字的关键就是作用域。

2.2.1 sizeof

  • sizeof()与结构体(来源:C和指针>结构的存储分配)

sizeof操作符能够得出一个结构的整体长度,包括因边界对齐而跳过的那些字节。如果你必须确定结构某个成员的实际位置,应该考虑边界对齐因素,可以使用offsetof宏(定义于stddef.h)。 offsetof( type,member ) type就是结构的类型,member就是你需要的那个成员名。表达式的结果是一个size_t值,表示这个指定成员开始存储的位置距离结构开始存储的位置偏移几个字节。例如,对前面那个声明而言, offsetof( struct ALIGN, b ) 的返回值是4。

2.2.2 关键字

volatile — 作用/使用场景

例程


int main(void){
    volatile int i;
    for(i=0; i<100; i++){...}
    ...
}  

演示:

解析:对于for()循环i++操作,对于变量i的自加操作将直接在cpu的内部寄存器中执行,提高效率;使用volatile修饰后,i++操作讲先从ram读出i的值而后再计算并返还结果到ram。这个操作对于普通变量并无意义,但对于寄存器则是必须的。

使用情景1:

volatile int *p; //未使用的全局变量,volatile防止编译器因为变量未使用而自动优化去掉;

使用情景2:

#ifndef S3C2440_SOC_H ​ #define S3C2440_SOC_H

#define REG(x) (*(volatile unsigned int *)(x)) ​ #define REG_BYTE(x) (*(volatile unsigned char *)(x))

/Memory Controllers/ ​ #define BWSCON REG(0x48000000) //Bus width & wait status control ​ #define BANKCON0 REG(0x48000004) //Boot ROM control

关键字作用原理:

(1) int i = 1; i++;

i++时,编译器可能会自动保存/备份变量到CPU寄存器中,i++修改时,将在CPU中直接执行,提高执行效率。

(2)volatile int i =1; i++;

i++时,将重新从RAM中读出i,然后再CPU中i++不进行优化

注:硬件寄存器必须用volatile。

  • 个人理解:

①对于非寄存器的变量,都是在ram中存储,而变量的操作是:

从ram读出到cpu通用寄存器->在cpu作数学运算并得到结果->保存结果到ram

这类变量的操作都必须通过cpu的通用寄存器来产生结果,因此,这类变量在cpu直接执行,不会产生错误。

②对于硬件寄存器中的输入GPIO/状态类寄存器,会根据外部电信号而自动变更状态(不经过cpu),因此这类寄存器(对于CPU来说寄存器和ram的变量一样,都是特定地址上的存储单元)不能通过CPU优化。

const

源码:


volatile const int d_cont = 0x12345678;
volatile const int d_const_array[10] = {1,2,3,};
int main(void)
{}  

结论:暂没证明const常变量保存在flash区,可能KEIL工具的原因。const修饰的只读的变量放在flash中,节省ram本身是正确的。

static

提问:老师,关于主题static的问题:a.c中定义static int add( ),并在(本文件)上面定义的函数还没生效时,调用add( )函数(其他文件,如b.c中也定义了全局的add()函数),是不是使用的是外部的全局函数?

答:韦老师代码有问题,本地测试会出错,测试未完成。

extern

摘要:外部(文件)变量的几种引用方式&建议方式、个人总结笔记


b.c:
int b;
a.c:
extern int b;  

对于外部(文件)变量的引用方法(例:b.c中定义变量int b并在a.c中引用):

  1. 可以直接在a.c中使用extern声明引用其他文件的全局变量:extern int b, 即可完成变量b的引用;

  2. 声明外部变量到某个“b.h”中并include(建议使用,减少耦合利于团队分工合作);

  3. 通过函数,得到b.c文件中的变量b的值并声明函数到b.h:int get_b();

  4. 变量使用,一般最好加上static;

注:不建议用全局变量,包括上面方式1/2,若想使用可以使用方式3,在不暴露变量的情况下使用函数包裹。

补:什么是回调函数和钩子函数?

img

提问1:外部定义的extern变量,需要怎么做才能引用?是需要include吗? 提问2:在外部的文件里定义的的变量声明为extern,编译器怎么能找的到?是不是一定要和引用文件同一级目录?如果子目录是不是就找不到了? 还是不太理解这个extern的意义。 直接引用头文件中的全局变量也不会有错误啊,extern感觉没有特别的意义啊。

提问3:extern的关键字应该由提供方添加还是应该由适用方添加?

提问4:老师,还有一个问题,就是编译器怎么知道去哪个路径里寻找extern的符号?是Makefile指定,还是默认当前路径?

答1:看上面总结。

答2:(1)a.c/b.c...编译时,会被编译成不同的a.o/b.o/...,然后所有xx.o文件将被链接成同一个xx.hex/xx.bin文件(所谓链接就是确定一下它们的地址)。(2)不需要同一目录。(3)头文件放全局变量会有问题,若如此操作,该头文件被多个文件包含并编译链接时,会造成重复定义。

答3:看上面总结。

答4:不找。

我的笔记:

1.对于对于外部(文件)变量的引用方法3的意义思考:使用函数封装变量,只传递变量值到使用方,可以避免使用方的某些不规范操作(如:内存越界或取址等)修改了变量的值。

2.3 struct引用结构体变量&结构体的赋值

摘要:结构体类型/变量的内存分配、数据结构(结构体变量的首地址/长度,成员变量的首地址、长度、填充字节数、内存分配是否连续)

2.3.1 引入结构体


/* Standard includes. */
#include <stdio.h>
​
/* 打印同学的姓名、年龄 */
struct person {
    char *name;
    int age;
};
struct person2 {
    char name[100];
    int age;
};
struct person3 {
    char sex;
    int age;
};
struct person4 {
    char sex;
    char high;
    int age;
};
​
struct company {
    char *name;
    struct person worker[100];
};
​
int main(void)
{
    struct person wei = {"weidongshan", 40};
    printf("sizeof(struct person) = %d, sizeof(struct person2) = %d\r\n", \
           sizeof(struct person), sizeof(struct person2));
​
    printf("sizeof(struct person *) = %d, sizeof(struct person2 *) = %d\r\n", \
           sizeof(struct person *), sizeof(struct person2 *));
​
    printf("sizeof(struct person3) = %d, sizeof(struct person3 *) = %d\r\n", \
           sizeof(struct person3), sizeof(struct person3 *));
​
    printf("sizeof(struct person4) = %d, sizeof(struct person4 *) = %d\r\n", \
           sizeof(struct person4), sizeof(struct person4 *));
​
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    while (1);
    
    return 0;
}  

imgimg

img

效率,并且有些硬件并不支持,例如使用奇数访问单个地址是合格的,但是作为基地址访问4字节内存就不合格。

2.3.2 问题

1.结构体类型占内存吗?

答:数据类型不占内存空间,变量才占。进行实例化/变量后,才占内存空间。

2.典型结构体案例


#include <stdio.h>
struct StudentInfo{ //占12字节
    char *name;
    int age;
    int high;
};
struct StudentInfo2{ //占12字节
    char num;
    int age;
    int high;
};
struct StudentInfo3{ //占20字节
    char name[10];
    int age;
    int high;
};
int main(void)
{
    printf("sizeof(struct StudentInfo)=%d, sizeof(struct StudentInfo2)=%d, sizeof(struct StudentInfo3)=%d\n", sizeof(struct StudentInfo), sizeof(struct StudentInfo2), sizeof(struct StudentInfo3));
    ...
}
/* Microsoft Visual C++6.0测试结果,32位:
sizeof(struct StudentInfo)=12, sizeof(struct StudentInfo2)=12, sizeof(struct Stu
dentInfo3)=20(四字节对齐,数组name[10]+未使用部分=分配12字节)  

3.结构体数据类型的元素内存分配问题

问题1:如该struct person4多字符元素结构体数据类型所占内存大小是否是6字节还是8/12字节?

问题2:对于由字符指针和数组元素组成的结构体变量,其内存空间大小是如何计算的?

答1:8字节。

img

对于结构体多字符类元素的内存使用情况如下:

img

答2:

(2)结构体变量赋值

总结

百科:Win32平台下的微软C编译器(cl.exefor 80×86)的对齐策略: 1)结构体变量的首地址是其最长基本类型成员的整数倍; 2)结构体每个成员相对于结构体首地址的偏移量(offset)都是(该)成员大小的整数倍,如有需要编译器会在成员之间加上填充字节; 3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节; 4)结构体内类型相同的连续元素将在连续的空间内,和数组一样。

C和指针: 系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐要求,因此所有结构的起始存储位置必须是结构中边界要求最严格的数据类型所要求的位置。

链接:[结构体的内存分配机制](https://www.cnblogs.com/qiumingcheng/p/11297988.html)

个人总结: 1.(32位)系统对于内存的分配和使用始终遵循4字节对齐,对于结构体变量的整体内存/元素内存分配仍遵循4字节(32位系统)/8字节(64位系统)对齐分配; 2.对于一次分配的4字节单位内存,若有相邻char/short等少于4字节的数据类型元素,将叠加处理; 3.使用sizeof查看的结构体变量内存,将是其实际占用的内存大小,且有可能小于4字节大小。


...
struct StudentInfo5{
    char num;
    char age2;
    int age;
};
struct StudentInfo6{
    char num;
    char age2;
};
struct StudentInfo7{
    short high2;
    short high3;
};
int main(void)
{
    printf("sizeof(struct StudentInfo5)=%d, sizeof(struct StudentInfo6)=%d, 
      sizeof(struct StudentInfo7)=%d\n", 
            sizeof(struct StudentInfo5), sizeof(struct StudentInfo6), \
    sizeof(struct StudentInfo7));
    return 0;
}
/*Microsoft Visual C++6.0执行结果:
sizeof(struct StudentInfo5)=8,sizeof(struct StudentInfo6)=2, 
sizeof(struct StudentInfo7)=4
Press any key to continue
*/  

4.探究数组成员对于结构体的意义

数组name[10]是一个元素,还是其每个数组元素都是一个结构体成员?

单从成员调用情况看,两种描述方式都正确,但从结构体类型成员内存分配机制的规则来看,应该数组的每个元素都被结构体当做一个结构体成员处理。


例程:
#include <stdio.h>
struct StudentInfo13{ //探究数组成员对于结构体的意义;
    char name[10];
    int high4;
};
struct StudentInfo14{
    char name[10];
    char high4;
};
int main(void)
{
    printf("sizeof(struct StudentInfo13)=%d\n", sizeof(struct StudentInfo13));  
    printf("sizeof(struct StudentInfo14)=%d\n", sizeof(struct StudentInfo14));  
​
    struct StudentInfo13 Huxiaohu= {"HuXiaohu", 174};
    printf("Huxiaohu.name = %s, Huxiaohu.high4=%d\n", Huxiaohu.name, Huxiaohu.high4);
    printf("Huxiaohu.name = %c, Huxiaohu.high4=%d\n", Huxiaohu.name[0], Huxiaohu.high4);
    return 0;
}
/*
sizeof(structStudentInfo13)=16
sizeof(struct StudentInfo14)=11
Huxiaohu.name = HuXiaohu, Huxiaohu.high4=174
Huxiaohu.name = H, Huxiaohu.high4=174
Press any key to continue
*/  

我的测试:


#include <stdio.h>
​
struct StudentInfo{
    char *name;
    int age;
    int high;
};
struct StudentInfo2{
    char num;
    int age;
    int high;
};
struct StudentInfo3{
    char name[10];
    int age;
    //int high;
};
struct StudentInfo4{
    char num;
    char age2;
    char age3;
    int age;
    int high;
};
struct StudentInfo5{
    char num;
    char age2;
    int age;
};
struct StudentInfo6{
    char num;
    char age2;
};
struct StudentInfo7{
    short high2;
    short high3;
};
​
struct StudentInfo8{
    char high2;
    short high3;
};
struct StudentInfo9{
    int high2;
    short high3;
};
struct StudentInfo10{
    short high3;
    char high2;
};
struct StudentInfo11{
    double high3;
    char high2;
};
struct StudentInfo12{
    char high2;
    char high4;
    double high3;
};
struct StudentInfo13{ //探究数组成员对于结构体的意义;
    char name[10];
    int high4;
    /*数组name[10]是一个元素,还是其每个数组元素都是一个结构体元素?*/
};
struct StudentInfo14{
    char name[10];
    char high4;
};
int main(void)
{
    printf("sizeof(struct StudentInfo)=%d, sizeof(struct StudentInfo2)=%d, sizeof(struct StudentInfo3)=%d, sizeof(struct StudentInfo4)=%d\n", sizeof(struct StudentInfo), sizeof(struct StudentInfo2), sizeof(struct StudentInfo3),sizeof(struct StudentInfo4));
    printf("sizeof(struct StudentInfo5)=%d, sizeof(struct StudentInfo6)=%d, sizeof(struct StudentInfo7)=%d\n", sizeof(struct StudentInfo5), sizeof(struct StudentInfo6), sizeof(struct StudentInfo7));
    printf("sizeof(struct StudentInfo8)=%d, sizeof(struct StudentInfo9)=%d, sizeof(struct StudentInfo10)=%d\n", sizeof(struct StudentInfo8), sizeof(struct StudentInfo9), sizeof(struct StudentInfo10));
    printf("sizeof(struct StudentInfo11)=%d, sizeof(struct StudentInfo12)=%d, sizeof(struct StudentInfo13)=%d\n", sizeof(struct StudentInfo11), sizeof(struct StudentInfo12), sizeof(struct StudentInfo13));    
    printf("sizeof(struct StudentInfo14)=%d\n", sizeof(struct StudentInfo14));  
​
    struct StudentInfo13 Huxiaohu= {"HuXiaohu", 174};
    printf("Huxiaohu.name = %s, Huxiaohu.high4=%d\n", Huxiaohu.name, Huxiaohu.high4);
    printf("Huxiaohu.name = %c, Huxiaohu.high4=%d\n", Huxiaohu.name[0], Huxiaohu.high4);
    return 0;
}
/*C++6.0结果:
sizeof(struct StudentInfo)=12, sizeof(struct StudentInfo2)=12, sizeof(struct StudentInfo3)=16, sizeof(struct StudentInfo4)=12
sizeof(struct StudentInfo5)=8, sizeof(struct StudentInfo6)=2, sizeof(struct StudentInfo7)=4
sizeof(struct StudentInfo8)=4, sizeof(struct StudentInfo9)=8, sizeof(struct StudentInfo10)=4
sizeof(struct StudentInfo11)=16, sizeof(struct StudentInfo12)=16, sizeof(structStudentInfo13)=16
sizeof(struct StudentInfo14)=11
Huxiaohu.name = HuXiaohu, Huxiaohu.high4=174
Huxiaohu.name = H, Huxiaohu.high4=174
Press any key to continue
​
64位Ubuntu执行结果:
sizeof(struct StudentInfo)=16, sizeof(struct StudentInfo2)=12, sizeof(struct StudentInfo3)=16, sizeof(struct StudentInfo4)=12
sizeof(struct StudentInfo5)=8, sizeof(struct StudentInfo6)=2, sizeof(struct StudentInfo7)=4
sizeof(struct StudentInfo8)=4, sizeof(struct StudentInfo9)=8, sizeof(struct StudentInfo10)=4
sizeof(struct StudentInfo11)=16, sizeof(struct StudentInfo12)=16, sizeof(struct StudentInfo13)=16
sizeof(struct StudentInfo14)=11
Huxiaohu.name = HuXiaohu, Huxiaohu.high4=174
Huxiaohu.name = H, Huxiaohu.high4=174
*/  

2.3.3 结构体赋值

注:变量修改值时写几个字节(到该变量的内存空间),取决于变量的类型。


/*目的:探讨变量内存越界操作的典型案例及防范*/
#include <stdio.h>
int main(void)
{
    char c_val = 'A';
    char c_val2 = 'B';
    int i_val = 0x12345678;
    int *p;
​
    printf("&c_val = %p, &c_val2 = %p, &i_val = %p\n", &c_val, &c_val2, &i_val);
    printf("c_val = 0x%c, c_val2 = 0x%c, i_val = 0x%x\n", c_val, c_val2, i_val);
​
    p = (int *)&c_val;
    *p = 'H';
    printf("c_val = 0x%c, c_val2 = 0x%c, i_val = 0x%x\n", c_val, c_val2, i_val);
​
    return 0;
}
/*
在C++6.0系统运行结果:
&c_val = 0018FF44, &c_val2 = 0018FF40, &i_val = 0018FF3C
c_val = 0xA, c_val2 = 0xB, i_val = 0x12345678
c_val = 0xH, c_val2 = 0xB, i_val = 0x12345678
Press any key to continue
​
在64位linux系统运行结果:
&c_val = 0x0x7fff8815958b, &i_val = 0x0x7fff8815958c
c_val = 0x42, i_val = 0x12000000
​
&c_val = 0x7ffc339b194a, &c_val2 = 0x7ffc339b194b, &i_val = 0x7ffc339b194c
c_val = 0xA, c_val2 = 0xB, i_val = 0x12345678
c_val = 0xH, c_val2 = 0x, i_val = 0x12340000
*/  

案例2:
/*目的:探讨变量内存越界操作的典型案例及防范*/
#include <stdio.h>
struct person {
    char c;
    char d;
    int age;
}; 
struct person wei = {'A', 'B', 0x12345678};
int main( void )
{
    int *p;
    p = (int *)&wei.c;
    *p = 'C';
    printf("wei.c = 0x%p, wei.d = 0x%p, wei.age = 0x%p\n", &wei.c, &wei.d, &wei.age);
    printf("wei.c = 0x%x, wei.d = 0x%x, wei.age = 0x%x\n", wei.c, wei.d, wei.age);
    printf("wei.c =   %c, wei.d = %c, wei.age = 0x%x\n", wei.c, wei.d, wei.age);
    return 0;
}
/*
wei.c =0x00424A30,wei.d =0x00424A31,wei.age = 0x00424A34
wei.c =0x43,wei.d =0x0,wei.age=0x12345678
wei.c= C,wei.d=  ,wei.age=0x12345678
Press any key to continue,
*/
  

2.4 类型转换

int <=== char

struct <=== struct pointer

2.5 指针&函数指针

2.5.1 变量赋值的解析

(1)C 指令 int a=123解析

a.通过cpu写指令;

b.怎么知道/如何写123?

I. cpu读flash得到指令(该C指令可能包括好几条汇编指令);

II. cpu执行指令

III. 写内存变量a


a = 123;  //隐含了地址的操作
等同于
int *p=&a;
*p=123;  

读/写数据到flash、sdram等存储单元的意义

个人理解:对于CPU来说,操作块flash、块sdram等存储器,以及变量,都属于同一类操作,都是对统一编址的存储单元的某部分进行读写。

2.5.2 通过指针赋值

如上。

2.5.3 结构体指针

(1)定义结构体类型时用结构体自身类型或指针定义内部成员的方法:

例程:定义一个包含学生信息结构体

正确示例:
struct student{
    char *name;
    int age;
    struct student *pclassmate;
};
typedef struct Student{
    char *name;
    int age;
    struct Student *classmate;
}Student_t, *Student_pt;
typedef struct Student{
    struct Student Exp1;        //结构体Student还没定义好,编译器不知道该类型所需多大空间,因此会报错
    struct Student * pExp2;     // pExp2是一个指针,OK
    void fun1(struct Student Stud3);    /*Stud3是一个结构体变量做函数参数:
                                        (1)函数参数在运行时才会压栈,因此编译是没有问题的;
                                        (2)运行时,结构体类型已经是定义好的,因此也不会有问题*/
    void fun2(struct Student * pStud4); //pStud4是一个指针做函数参数,也是OK的
}Student_t, *Student_pt;
注:该例子在说明定义自身类型成员以及指针时,用的是struct Student,而不是Student_t。
​
错误示例:
struct student{
    char *name;
    int age;
    struct student classmate; //结果:无限套娃了,结构体的大小递归编译器无法计算,应使用结构体指针。
};
typedef struct {
    char *name;
    int age;
    struct Student *classmate;
}Student, *pt_Student;

总结:

定义自身类型的成员是不可以的 定义结构体时,是不可以定义自身类型的成员的。这是因为结构体中各个成员所需的存储空间大小是编译阶段确定的,当用该结构体定义自身成员时,由于结构体大小还不确定,因此此时定义的陈冠所需要的存储空间大小也就不确定,因此编译会报错; 定义自身类型的指针成员是可以的 但是可以定义该结构体类型的指针,因为指针在固定的平台上所占的内存大小是确定的! 定义成员函数时,结构体类型做函数参数类型是可以的 函数参数在运行时才会压栈,因此编译是没有问题的;运行时,结构体类型已经是定义好的,因此也不会有问题。 定义成员函数时,结构体指针做函数参数类型是可以的 指针做函数参数,没有问题; 原文链接:定义结构体类型时用结构体自身类型或指针定义内部成员_mengshushu90的博客-CSDN博客

结构体变量&结构体指针作为函数参数

结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,非常快速。

2.5.4 函数指针

摘要:函数指针的本质,函数指针变量的定义方式、强制类型转换、使用方法,公司使用案例(如何使程序支持(并自动识别)多款(同类型,可相互替代)设备)

(1)函数指针也是地址

《C程序设计语言》5.11 指向函数的指针: 在C语言中,函数本身不是变量,但可以定义指向函数的指针。这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。

个人理解: 函数也是有地址的,有地址就可以被指针变量指向。

函数指针变量的定义方式:


变量指针:int *p;
函数指针:void (*play_ball)(void);//定义一个(函数指针)变量(它不再是一个函数),在内存里必定有一块内存来保存它;
  

(2)我的测试 & 强制类型转换 & 使用方法

#include <stdio.h>
​
void (*Specialty_p)(int a, int b);
void add(int a, int b)
{   }
int main(void)
{
    int val = 10;
    int *p = &val;
​
    Specialty_p = add;
    printf("&val = 0x%p, Specialty_p = 0x%p \n", p, Specialty_p);  //&val = 0x0018FF44, Specialty_p = 0x0040100F;
​
    Specialty_p = (void (*)(int, int))p;
    printf("&val = 0x%p, Specialty_p = 0x%p \n", p, Specialty_p);  //&val = 0x0018FF44, Specialty_p = 0x0018FF44;
​
    printf("func pointer len = %d\n", sizeof(Specialty_p));  //结果:func pointer len = 4;
    return 0;
}
总结:
    1.函数指针大小4字节;
    2.函数指针是一种指向特定类型函数的指针,同结构体类型指针类似且同等级。
    3.强制类型转换的方法:类似(void (*)(int, int)),去掉指针名字(*后面位置)和形参名字,保留结构;
    4.函数名的本身或者取地址,都是同一个函数的地址,特权;
    5.函数指针和函数的用法完全一样。
#include <stdio.h>
​
typedef struct Student{
    char *name;
    int age;
    struct Student *classmate;
    void (*Specialty_p)(void);
}Student_t, *pt_Student;
void (*Specialty_p)(void);
​
static void play_ball(void){
    printf("play ball \n");
}
static void sing_song(void){
    printf("sing_song \n");
}
int main(void)
{
    Student_t Stu[2] = {
        {"ZhangSan", 31, NULL, play_ball},
        {"LiSi", 28, NULL, sing_song},
    };
    Stu[0].classmate = &Stu[1];
    Stu[1].classmate = &Stu[0];
    int i;
    for(i = 0; i < 2; i++)
    {
        printf("%s's classmate is: %s, specialty is: ", Stu[i].name, Stu[i].classmate->name);
        Stu[i].Specialty_p();
    }
    /*
    ZhangSan's classmate is: LiSi, specialty is: play ball
    LiSi's classmate is: ZhangSan, specialty is: sing_song
    Press any key to continue
    */
}

(3)案例-如何使程序支持(并自动识别)多款设备

公司某产品有多款LCD屏幕(LCD_A、LCD_B...),如何使程序支持(并自动识别)多款设备。


方式1:通过宏判断;
//#define LCD_TYPE A
int main(void){
#ifdef LCD_TYPE_A
    draw_logo_lcda();
#else
    draw_logo_lcdb();
#endif
    while (1);
    return 0;
}
//缺点:每换一种屏幕,都需要修改代码,至少需要修改宏LCD_TYPE,重新编译重新烧写。
  

方式2:假若LCD屏幕自带EEROM,内含设备号等信息,则可通过读取type来自动判断识别设备;
    //根据硬件的信息,动态的判断调用各种函数。
int read_eeprom(void){ //对于真实的产品,应该读flash,读EEROM
    /* 0:lcd a; 1:lcd b */
    return 0;
}
int get_lcd_type(void){ 
    return read_eeprom ();
}
int main(void){
    int type = get_lcd_type();
    if(type == 0)
        draw_logo_lcda();
    else if(type == 1)
        draw_logo_lcdb();
    while (1);
    return 0;
}
//缺点:当公司产品线较多,有很多LCD屏幕时,则需要做很多判断,代码冗余。
  

方式3:在方式2基础上优化
typedef struct Lcd_operation{
    int type;
    void (*draw_logo)(void);
}Lcd_operation_t, *Lcd_operation_pt;
static Lcd_operation_t xx_lcds[] = {
    {0, draw_logo_lcda()},
    {1, draw_logo_lcdb()}
};
int read_eeprom(void){ //对于真实的产品,应该读flash,读EEROM
    /* 0:lcd a; 1:lcd b */
    return 0;
}
int get_lcd_type(void){ 
    return read_eeprom ();
}
Lcd_operation_pt get_lcd(void){ 
    int type = get_lcd_type();
    return &xx_lcds[type]; //1.为什么加&?加或不加&都正确,但&则开销更小效率更高,返回地址只传输4字节,而传输整个结构体则需要传输8字节。 2.尽量避免使用全局变量(如:xx_lcds),若想使用,用函数封装起来。好处:获得硬件类型的函数修改了,只需要来这里修改下就好(分离分层?各层模块化修改最小?)
}
int main(void){
    Lcd_operation_pt lcd_present = get_lcd();
    lcd_present->draw_logo();
    while (1);
    return 0;
}
//代码变得很漂亮了,
/*C语言声明/定义数组变量时,可不指定数组大小的情况:(只有以下三种情况)
(1)数组是形式参数
(2)数组声明的同时进行了初始化
(3)数组的存储类型为extern
参考自:《C语言程序设计:现代方法》P334  */  

问题:

提问1:结构体为什么可以节省硬件资源?

答:使用结构体可以很方便的编写程序,且当程序设计的好的话,可以节省一部分硬件资源(如上面3相较于2节省了判断,从而节省了资源)。

提问2:老师,曾经看到过,在一个结构体初始化中这样写{read=xx,.wite=xx},这个.read和.write是指明属性赋值吗?

提问3:老师,结构体的点初始化是不是把C99那个勾上就行了啊?(C语言在1989年出了一个C89标准,1999年出了一个C99标准,2011年出了一个C11标准(最新的))


lcd_operation lcd_a_ops = {
    .type =0,
    .draw_logo = draw_logo_lcda,
};
//答2/3:这是C99的扩展用法(KEIL需要单独勾选),可以只初始化用到的结构体成员,更方便初始化。  

提问4:typedef定义结构体变量的别名能不能好好讲讲,有时候理不清楚


typedef int (*add_type) (int, int); //typedef函数指针类型重定义;
add_type add1, add2;  //定义两个int (*) (int, int)类型函数指针;  

3.链表操作

3.1 基础链表


//案例:单线间谍组织名单输出;
typedef struct spy {
    char *name;
    struct spy *next;
}spy, *p_spy;
spy A = {"A", NULL};
spy B = {"B", NULL};
spy C = {"C", NULL};
​
int main( void ){
    p_spy head = NULL;
    
    A.next = &B;
    B.next = &C;
    C.next = NULL;
​
    head = &A;
    while (head)
    {
        printf("%s\r\n", head->name);
        head = head->next;
    }
    return 0;
}  

课堂图:

3.2 链表插入

3.3 链表删除


//在链表末尾插入新节点;
typedef struct spy {
    char *name;
    struct spy *next;
}spy, *p_spy;
spy A = {"A", NULL};
spy B = {"B", NULL};
spy C = {"C", NULL};
spy D = {"D", NULL};
p_spy head = NULL;
​
void insert_spy(p_spy newspy)
{
    p_spy last;
    
    if (head == NULL)
    {
        head = newspy;
        newspy->next = NULL;
    }
    else
    {
        /* 先找到链表的最后一项 last */
        last = head;
        while (last)
        {
            if (last->next == NULL) /* 找到了 */
                break;
            else
                last = last->next;
        }
        last->next = newspy;
        newspy->next = NULL;
    }
}
void remove_spy(p_spy oldspy)
{
    p_spy left;
    
    if (head == oldspy)
    {
        head = oldspy->next;
    }
    else
    {
        /* 找出oldspy的上线 */
        left = head;
        while (left)
        {
            if (left->next == oldspy)
                break;
            else
                left = left->next;
        }
        
        if (left)
        {
            left->next = oldspy->next;
        }
    }
}
void print_spy(void)
{
    p_spy tmp = head;
    while (tmp)
    {
        printf("%s\r\n", tmp->name);
        tmp = tmp->next;
    }
}
int main( void )
{
    insert_spy(&A); 
    insert_spy(&B); 
    insert_spy(&C); 
    insert_spy(&D); 
​
    print_spy();
    
    remove_spy(&B);
    printf("remove spy B: \r\n");
    
    print_spy();
    while (1);
    return 0;
}  

4.ARM架构

flash只会存储汇编指令,一条基础汇编指令4字节。ARM架构是精简指令集,对内存来说,只有读/写两个操作,所有的计算都是在cpu内部实现的。


例程:
int main( void ){
    volatile int a = 1;
    volatile int b = 2;
    a = a + b;
    return 0;
}  

 

5.几条汇编指令(堆、栈)

  • load、store

  • add、sub

  • and/bic

  • B、BL(Branch)

一个程序运行的本质是什么,全局变量是怎么被设置的,局部变量是在哪里,需要看一个完整程序的反汇编

6. 几个核心问题

6.1 有值的全局变量的初始化

全局变量的初值来自flash,其初始化方式分两种:

(1)对所有全局变量依次单个赋值初始化


LDR R0, =0X12345678     //伪指令,等同于:MOV R0, #0X12345678
即:
LDR R1, [Flash_addr]
STR R1, [g_xx_ram_addr]  

缺点:当数据量很大时,效率很低。

(2)类似memcpy,把Flash上的数据段,整体拷贝到RAM(系统实际使用的方式)

6.2 全局变量的在内存/flash掉电特点:

内存掉电丢失,flash掉电不丢失。

6.3 初始值为0、没有初始化的全局变量,怎么初始化?

  • 有100万个这样的变量

  • 这100万个变量,都保存在Flash上吗?不。

  • 这些变量在内存里都放在ZI段(即.bss段)

    堆栈、BSS段、代码段、数据段、RO、RW、ZI等概念区分

  • 类似memset,把ZI段全部清零

6.4 (完成前面之后)才去调用main函数

6.5 局部变量在哪?

在栈里。

6.6 局部变量的初始化

(1)局部变量的初值在.text代码中;

(2)初始化或赋值,需要一个一个的写;

6.7 栈的作用

(1)函数嵌套使用时,LR寄存器(保存函数的返回地址)会被覆盖,因此,进入函数后,需要保存LR寄存器到栈中;

(2)保存R0~R3寄存器中的值;


int g_a3 = 0;
int main(void)
{
    volatile int a = l;
    volatile int b = 2;
    a = a+b;
​  

 提问1:

提问2:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言加强--韦老师公开课 的相关文章

  • (二)五次多项式轨迹规划

    一 三次多项式轨迹规划的缺陷 上一篇文章说道 xff0c 三次多项式轨迹规划只能够保证速度和位移连续 xff0c 并不能保证加速度连续 加速度不连续将会对使电机抖动 甚至冲击 二 轨迹规划中的五次多项式 我们对加速度数值进行指定 xff0c
  • (三)抛物线过渡的线性函数规划

    前面说到 xff0c 无论是三次还是五次多项式进行规划存在以下缺点 xff1a 位移往返没有匀速段 这一节中 xff0c 我们的研究对象是初速度和末速度都为0关节运动 一 无过渡线性函数 假设时刻 t t t 和角度
  • GCC的学习(二)头文件及其库制作

    当前目录 43 相对路径 61 绝对路径大写i xff0c I头文件包含路径大写l xff0c L库文件路径小写l xff0c l库名字 xff08 去头去尾没有lib xff0c 也没有so ldd 查看动态库连接库存在性及其路径nm 查
  • VSCODE(八)launch 调试与运行

    前面讲了如何配置任务文件tasks json xff0c 调试功能在程序程序经常会用上 xff0c 那么VSCODE是完成调试功能的呢 xff1f 答 xff1a vscode文件夹内的launch json文件配置 xff0c 一些调试器
  • C++11线程库 (七) 线程的停止

    在这篇文章里 xff0c 我们将会讨论如何在C 43 43 11下停止和终止一个线程 为什么C 43 43 11不直接提供一个停止线程的方法 xff1f 这是因为线程在停止之前可能有一些资源需要被释放 关闭 xff0c 比如说 xff1a
  • QCustomplot(五)QCPAxisRect进行子绘图

    子绘图是比较复杂的 xff0c 你几乎需要掌握QCustomPlot所有类及其含义 那么需要掌握的类有 xff1a QCPAxis 描述轴行为 xff0c 轴需要依赖QCPAxisRectQCPAxisRect 描述矩形区域 xff0c 区
  • 关于C/C++的换行

    可以分为两种情况 xff1a 代码换行长字符串换行 一 C C 43 43 代码换行直接用enter键换行 xff0c 同时注意关键字不要切割 xff1a span class token keyword bool span a span
  • 31线性变换及其矩阵

    一 线性变换的概念 线性变换是数学中一种特殊的映射 xff0c 它具有将向量空间中的元素转换到另一向量空间元素的功能 xff0c 同时也保持了变换前后向量的线性不变的性质 线性变换的数学表达式如下 xff1a T c
  • 30奇异值分解

    在讲SVD之前 xff0c 我们先来看看计算是如何存储一个灰度图的 灰度图会被分割成一块块的小灰度像素 xff0c 然后进行存储 xff0c 数值的大小表示灰度的程度 一个灰度像素用8位进行存储 xff0c 也就是256种状态 xff0c
  • (二)PID控制的Anti-windup

    比例环节 xff1a 快速接近目标积分环节 xff1a 防止稳态误差微分环节 xff1a 减少振荡 被控对象可以分为两个部分 xff0c 分别是执行器 xff0c 用于产生力或者能量从而改变系统 xff0c 和处理 xff0c 比如说温控的
  • Socket的学习(一)什么是Socket?

    本文参考的是 Socket通信原理 https www cnblogs com wangcq p 3520400 html 一 TCP IP UDP是什么 xff1f TCP IP xff08 Transmission Control Pr
  • Socket的学习(二)AF_UNIX实现本地通信

    参考文章 xff1a 1 Unix Domain Socket IPC通信机制 2 How fast are Unix domain sockets 3 read 函数参数理解 一 Unix域的Socket通信及其优点 基于socket的框
  • 工程师笔记|在CMD窗口运行C语言

    cmd窗口大家可能都比较熟悉 xff0c 那如何在cmd窗口编译运行C语言程序呢 xff1f 其实很简单的 xff0c 只需要下载一个C C 43 43 编译器 xff0c 添加一下环境变量 xff0c 之后就能在cmd窗口中编译运行C语言
  • 推挽输出与开漏输出

    推挽输出 要理解推挽输出 xff0c 首先要理解好三极管 xff08 晶体管 xff09 的原理 下面这种三极管有三个端口 xff0c 分别是基极 xff08 Base xff09 集电极 xff08 Collector xff09 和发射
  • 固件、驱动、软件的区别

    不管我们使用什么操作系统 xff0c 无论是 Windows macOS xff0c 还是 Linux xff0c 里面都安装了许多软件 驱动程序和固件 但是 xff0c 这三者概念有什么区别 xff1f 转载微信公众号 xff1a 良许L
  • 51单片机与STM32的区别(为何51单片机IO引脚的驱动能力弱)

    1 51单片机的特性 51单片机之所以成为经典 xff0c 成为易上手的单片机主要有以下特点 xff1a 从内部的硬件到软件有一套完整的按位操作系统 xff0c 称作位处理器 xff0c 处理对象不是字或字节而是位 不但能对片内某些特殊功能
  • I2C—读写EEPROM

    1 I2C协议简介 I2C通讯协议 Inter xff0d IntegratedCircuit 是由Phiilps公司开发的 xff0c 由于它引脚少 xff0c 硬件实现简单 xff0c 可扩展性强 xff0c 不需要USART CAN等
  • SPI—读写串行FLASH

    1 SPI协议简介 SPI协议是由摩托罗拉公司提出的通讯协议 SerialPeripheralInterface xff0c 即串行外围设备接口 xff0c 是一种高速全双工的通信总线 它被广泛地使用在ADC LCD等设备与MCU间 xff
  • C语言中的关键字应用技巧(volatile、const、struct/union、_ _预定义_ _、#/##、void/void*、weak)

    嵌入式C开发关键字的应用技巧 1 volatile volatile修饰表示变量是易变的 xff0c 编译器中的优化器在用到这个变量时必须每次都小心地从内存中重新读取这个变量的值 xff0c 而不是使用保存在寄存器里的备份 xff0c 有效

随机推荐

  • 一文弄懂GPIO不同模式之间的区别与实现原理

    GPIO全称General Purpose Input Output xff0c 即通用输入 输出 其实GPIO的本质就是芯片的一个引脚 xff0c 通常在ARM中所有的I O都是通用的 不过 xff0c 由于每个开发板上都会设计不同的外围
  • printf()是如何与UART外设驱动函数“勾搭”起来的?

    今天给大家分享的是IAR下调试信息输出机制之硬件UART外设 在嵌入式世界里 xff0c 输出打印信息是一种非常常用的辅助调试手段 xff0c 借助打印信息 xff0c 我们可以比较容易地定位和分析程序问题 在嵌入式应用设计里实现打印信息输
  • 卡塔尔世界杯:带“芯片”的智能足球亮相!背后藏着哪些技术原理?

    2022年卡塔尔世界杯正式开幕 xff01 揭幕战上 xff0c 厄瓜多尔队以2 0的比分击败东道主卡塔尔队 xff0c 取得本届世界杯的首场胜利 本场比赛后 xff0c 世界杯东道主首战不败的纪录就此作古 xff0c 这一消息也引发足球圈
  • 串口通信详解

    一 串口通讯简介 串口通信 Serial Communications 的概念非常简单 xff0c 串口按位 bit 发送和接收字节 尽管比按字节 byte 的并行通信慢 xff0c 但是串口可以在使用一根线发送数据的同时用另一根线接收数据
  • C++类详解(public、private、protected)

    二 C 43 43 类的声明 类使用class关键字声明 xff0c 声明方法如下 xff1a class 类名 xff1a public 公有成员 int num private 私有成员 int age protected 保护成员 i
  • linux下网络通信(udp通信协议详解)

    一 udp通信简介 udp是User Datagram Protocol的简称 xff0c 中文名是用户数据报协议 udp协议位于osi模型中的传输层 xff0c 它是一种面向无连接的协议 udp协议并不保证数据一定能够到达对端 xff0c
  • 广播地址、组播地址、网关和子网掩码

    一 IP地址分类 IP地址一共32位 xff0c 由两部分组成 xff0c 网络号和主机号 网络号标识当前设备处于Internet的哪一个网络 xff0c 主机号标识当前设备属于该网络中的那一台主机 IP地址一共分为5类 xff1a 地址分
  • URL格式

    一 URL基本格式 一个完整的url包含方案 用户名 密码 主机名 端口 路径 参数 查询和片段 xff0c 格式如下 xff1a lt scheme gt lt user gt lt password gt 64 lt host gt l
  • c++构造函数和析构函数

    一 构造函数和析构函数的特点 构造函数和析构函数是一种特殊的公有成员函数 xff0c 每一个类都有一个默认的构造函数和析构函数 xff1b 构造函数在类定义时由系统自动调用 xff0c 析构函数在类被销毁时由系统自动调用 xff1b 构造函
  • linux下常用压缩命令

    一 tar命令 tar命令用来打包一个目录 xff0c 它支持三种格式 xff1a tar bz2 34 和 gz 34 1 1 压缩 tar cvf 文件名 tar 文件目录 打包成 tar文件 tar jcvf 文件名 tar bz2
  • 用vscode开发autojs,输出窗口不显示任何输出结果

    我的情况是 xff1a 我vscode开发autojs 程序 xff0c 之前在一切正常的情况下 xff0c 输出窗口可以正常显示程序运行结果 xff0c 右侧红圈里可以选择我连接的手机型号 如下图 xff1a 但是现在出现问题 xff1a
  • ubuntu开机没有ens33解决方法

    最近重新安装了VMware xff0c 使用之前的ubuntu镜像 xff0c 发现只有一个lo网卡 xff0c 没有ens33 xff0c 虚拟机无法获取ip地址 xff0c samba服务器也无法正常使用 root 64 ubuntu
  • ubuntu下arm-none-eabi-gcc安装

    一 下载安装包 下载地址 xff1a https launchpad net gcc arm embedded 43 download 选择linux版本下载 xff1a gcc arm none eabi 5 4 2016q3 20160
  • 2.4G-WiFi连接路由器过程

    一 概述 WiFi的数据通信基于802 11协议进行 xff0c 无线AP在工作时会定时向空中发送beacon数据包 xff0c 基站 xff08 STA xff09 从beacon中解析出AP的名称 加密方式等信息 xff0c 从而发起连
  • STM32f103时钟树详解

    一 概述 stm32有四种时钟信号源 xff0c HSE 高速外部时钟 HSI xff08 高速内部时钟 xff09 LSE xff08 低速外部时钟 xff09 LSI xff08 低速内部时钟 xff09 HSE通常接8M晶振 xff0
  • 头文件重复包含

    一 头文件重复包含问题分析 1 问题重现 举例说明 假设在某个C 43 43 头文件 或 源文件 中 xff0c 包含了A h和B h两个头文件 xff1a span class token macro property span clas
  • Netty 学习(六)实现自定义协议通信

    目录 前言一 通信协议设计通用协议自定义协议网络协议需要具备的要素1 魔数2 协议版本号3 序列化算法4 报文类型5 长度域字段6 请求数据7 状态8 校验字段9 保留字段 二 Netty 实现自定义通信协议Netty 中编解码器分类 三
  • ABB机器人与上位机进行Socket通信的RAPID代码实现(服务端)

    文章目录 前言1 实现的功能2 建立Socket通信2 1 ABB机器人的IP地址 xff1a 2 2 SocketAccept的说明 3 服务端接发信息3 1 核心代码3 2 CurrentPos函数 4 完整代码5 实现效果 前言 本文
  • ros使用自定义消息时,编译不成功,在Cmake中报错.

    在使用自定义消息时 xff0c 按照教程添加依赖和cmakelist文件后 xff0c 保证几条Cmake语句顺序无误的情况下 xff0c 考虑msg文件夹的位置 xff0c 应在功能包的第一级目录中
  • C语言加强--韦老师公开课

    目录 1 变量与指针 摘要 xff1a 普通变量 指针变量所占的内存空间大小 xff0c 变量在内存中的分配方式 xff08 首地址要求 长度 分配长度与实际使用长度区别 需要填充字节数 全局与局部变量在内存中的位置 xff09 xff1b