嵌入式准备

该博客围绕C/C++展开,介绍了static、const等关键字作用,阐述堆和栈的区别、内存分配方式及相关问题。还讲解了指针和引用的概念与区别,涉及变量声明定义、函数调用、中断处理、进程线程等知识,最后介绍TCP/IP协议及main函数前操作等。

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

一、关键字static、const、extern、volatile作用

1、const

1.修饰常量

用const修饰的变量是不可变的,修饰后的变量只能使用,不能修改。

2.修饰指针

如果const位于*的左侧,eg:const int* a,则const就是用来修饰指针所指向的变量,此时为常量指针,即指针指向*a为常量;
如果const位于*的右侧,eg:int* const a,const就是修饰指针本身,此时为指针常量,即指针本身a是常量。

const int* p = &a; // 常量指针;指针指向的内容不能变,*p不能变

int* const p = &a; // 指针常量;指针本身不能变, p不能变

3.修饰函数参数

用const修饰函数参数,传递过来的参数在函数内不可以改变。

void func (const int &n)
{
     n = 10;        // 编译错误 
}

2、static (静:是一种约束,不能乱跑,乱动)

1.修饰变量

(1)修饰局部变量

局部变量没有被static修饰:    存储在栈中          自动初始值不固定

局部变量被static修饰: (叫做局部静态变量)存储在全局数据区   初始化为0

代码运行,分析:

子函数变量中不加static进行修饰

#include <stdio.h>
 
void test()
{
    static int a = 0;
    a++;
    printf("%d ", a);
}
int main()
{
    int i = 0;
    for (i = 0; i < 8; i++)
    {
        test();
    }
    return 0;
}

子函数中加static进行修饰

#include <stdio.h>
 
void test()
{
    static int a = 0;
    a++;
    printf("%d ", a);
}
int main()
{
    int i = 0;
    for (i = 0; i < 8; i++)
    {
        test();
    }
    return 0;
}

(2)修饰全局变量

全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。

全局变量没有被static修饰: 整工程所有文件可以用这个全局变量(其它文件使用时候需要extern外部声明)也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。

全局变量被static修饰:静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。

static定义全局变量的作用:在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。

2.修饰函数

在函数的返回类型前加上static,就是静态函数。

静态函数:只能在声明它的文件中可见,其他文件不能引用该函数,不同的文件可以使用相同名字的静态函数,互不影响。

非静态函数:可以在另一个文件中直接引用,甚至不必使用extern声明。

3.C++面向对象中static用法

(1)类内静态数据成员

作用:实现多个对象共享数据的目标。

static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 m_total (下面程序中)分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。

class Student{
public:
    Student(char *name, int age, float score);
    void show();
public:
    static int m_total;  //静态成员变量
private:
    char *m_name;
    int m_age;
    float m_score;
};
(2)使用方法

初始化静态成员变量:

static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。

int Student::m_total = 0;

静态成员变量访问:

static 成员变量既可以通过对象来访问,static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。具体来说,static 成员变量和普通的 static 变量类似,都在内存分区中的全局数据区分配内存。

//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu("小明", 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student("李华", 16, 96);
pstu -> m_total = 20;

3、extern

1.修饰变量

若一个变量需要在同一个工程中不同文件里直接使用或修改,则需要将变量做extern声明。只需将该变量在其中一个文件中定义,然后在另外一个文件中使用extern声明便可使用,但两个变量类型需一致。

方式一:

在1.c中定义全局变量
int i; 
 
在2.c和3.c中都用 
extern int i;
 
声明一下就可以使用了

方式 二:

在头文件a.h 中声明 
extern int i; 
 
在其他某个c文件中定义
int i =0; 
 
其他要使用i变量的c源文件只需要include"a.h"就可以

2.修饰函数

在定义函数时,如果在函数首部的最左端冠以关键字extern,则表示此函数是外部函数,可以供其他文件调用。C语言规定,如果在定义函数时省略extern,则隐含为外部函数。在文件中要调用其他文件中的外部函数,则需要在文件中用extern声明该外部函数,然后就可以使用。

和定义全局变量 方式二比较相同呢。

4、volatile

1.修饰变量

编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问,这点在单片机这种存储器有限制的程序中需要注意。

声明时语法:
int volatile vInt;
volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;

当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错(可能某些寄存器值,被中断程序改变),所以说 volatile 可以保证对特殊地址的稳定访问。

二、内存分配&堆和栈

1、堆和栈

1.栈(stack=stand)

我叫它客栈,人来人往,只是大家伙(变量)临时休息的地方。

(1)编译器自动申请和释放空间

(2)栈向着内存减小的方向生长。

(3)栈有静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 malloc 函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。

(4)栈的效率比堆高很多

2.堆(heap)

(1)程序员手动申请和释放

(2)堆生长方向是向上的,也就是向着内存地址增加的方向

(3)堆都是动态分配的,没有静态分配的堆

2、内存分配

1.一个C/C++编译的程序占用内存分为以下5个部分

栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
全局区(静态区static):存放全局变量、静态数据、常量。程序结束后由系统释放。全局区分为已初始化全局区(data)和未初始化全局区(bss)

代码区:存放函数体(类成员函数和全局区)的二进制代码。
常量区(文字常量区):存放常量字符串,程序结束后有系统释放。

2.动态内存分配

主要说明两种方式:malloc/free和malloc/free不同之处

(1)malloc/free是C/C++标准库的函数,new/delete是C++操作符
(2)申请内存位置不同

new操作符是从自由存储区上为对象动态分配内存空间的,malloc函数是从堆上动态分配内存。
自由存储区是C++基于new操作符的一个抽象概念, 凡是通过new操作符进行内存申请的, 该内存称为自由存储区。 而自由存储区的位置取决于operator new的实现细节。自由存储区不仅可以是堆, 也可以是静态存储区, 取决operator new在哪里为对象分配内存

(3)定制内存大小不同

malloc/free需要手动计算类型大小,而new/delete编译器可以自己计算类型大小。

(4)返回值类型不同

new操作符内存分配成功时, 返回的是对象类型的指针,而malloc返回的是void*指针, 需要通过强转才能转成我们所需要的类型。new内存分配失败时会直接抛bac_alloc异常, 它不会返NULL, malloc分配内存失败时返回NULL

(5)malloc/free只是动态分配内存空间/释放空间。

new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)

(6)malloc/free和malloc/free使用方法

使用中多数遇到   C++类对象的内存空间动态分配用new/delete,而C/C++中指针对象空间动态分配用malloc/free。

3、内存分配重要问题

(1)C++中new申请的内存, 可以用free释放吗

不可以,new对应delete不可以张冠李戴。malloc/free,new/delete必需配对使用。

malloc与free是c++、c语言的标准库函数,new、delete是c++的运算符。它们都可用用申请动态内存和释放内存。对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此c++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。new和delete的本质是一个函数,malloc和free只是这两个函数中的一句调用语句,

创建对象和释放对象具体调用是,new分配内存时,先调用malloc后调用构造函数,释放空间时,先调用析构函数,后调用free。

(2)delete和delete[]的区别

假如使用new[]创建一个对象数组(对象数组?),则delete只会调用第一个对象的析构函数(造成内存泄漏),而delete[]会调用每个成员的析构函数。
用new分配的内存用delete释放,用new[]分配的内存用delete[]释放。

三、指针和引用

1、概念

1.指针

从本质上讲是一个变量,变量的值是另一个变量的地址,指针在逻辑上是独立的,它可以被改变的,包括指针变量的值(所指向的地址)和指针变量的值对应的内存中的数据(所指向地址中所存放的数据)。

2.引用

从本质上讲是一个别名,是另一个变量的同义词,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化(先有这个变量,这个实物,这个实物才能有别名),而且其引用的对象在其整个生命周期中不能被改变,即自始至终只能依附于同一个变量(初始化的时候代表的是谁的别名,就一直是谁的别名,不能变)。

2、区别

指针:变量,独立,可变,可空,替身,无类型检查;

引用:别名,依赖,不变,非空,本体,有类型检查;

3、在函数参数和返回值的传递方式(值传递、指针传递和引用传递

1.值传递

  以下是“值传递”的示例程序。由于Func1 函数体内的x是外部变量n 的一份拷贝,改变x 的值不会影响n, 所以n 的值仍然是0.

void Func1(int x)  
{  
    x = x + 10;  
}  
int n = 0;  
Func1(n);  
cout << “n = ” << n << endl;// n = 0  

2.指针传递

以下是“指针传递”的示例程序。由于Func2 函数体内的x 是指向外部变量n 的指针,改变该指针的内容将导致n 的值改变,所以n 的值成为10.(指针传递的时候,好像是又拷贝了一份数据到内存中,进行使用)

void Func2(int *x)  
{  
    (* x) = (* x) + 10;  
}  
⋯  
int n = 0;  
Func2(&n);  
cout << “n = ” << n << endl; // n = 10  

3.引用传递

以下是“引用传递”的示例程序。由于Func3 函数体内的x 是外部变量n 的引用,x和n 是同一个东西,改变x 等于改变n,所以n 的值成为10.

void Func3(int &x)  
{  
    x = x + 10;  
}  
//...  
int n = 0;  
Func3(n);  
cout << “n = ” << n << endl; // n = 10  

四、变量/函数的声明和定义之间有什么区别

声明:变量/函数的声明仅声明变量/函数存在于程序中的某个位置也就是后面程序会知道这个函数或者变量的类型,但不分配内存
定义:关于定义,当我们定义变量/函数时,除了声明的作用外,它还为该变量/函数分配内存。

五、函数调用过程

总结起来整个过程就三步:
1)根据调用的函数名找到函数入口;
2)在栈中审请调用函数中的参数及函数体内定义的变量的内存空间
3)函数执行完后,释放函数在栈中的申请的参数和变量的空间,最后返回值(如果有的话)

 1 int Add(int x,int y)
 2 {
 3     int sum = 0;
 4     sum = x + y;
 5     return sum;
 6 }
 7 
 8 int main ()
 9 {
10     int a = 10;
11     int b = 12;
12     int ret = 0;
13     ret = Add(a,b);
14     return 0;
15 }

(1)main函数执行

1、参数拷贝(参数实例化)分配内存。
2、保存当前指令的下一条指令,并跳转到被调函数。

(2)调用Add函数执行的一些操作
1、移动ebp、esp形成新的栈帧结构。
2、压栈(push)形成临时变量并执行相关操作。
在一个栈中,依据函数调用关系,发起调用的函数(caller)的栈帧在下面(高地址方向),被调用的函数的栈帧在上面。
每发生一次函数调用,便产生一个新的栈帧,当一个函数返回时,这个函数所对应的栈帧被清除(eliminated)
3、return一个值。
这些操作在Add函数中进行。

(3)被调函数完成相关操作后需返回到原函数中执行刚才保存的下一条指令
1、出栈(pop)。
2、恢复main函数的栈帧结构。(pop )
3、返回main函数
这些操作也在Add函数中进行。 至此,在main函数中调用Add函数的整个过程已经完成。

六、程序被中断、如何保护现场

链接:简述处理器中断处理的过程(中断向量、中断保护现场、中断嵌套、__牛客网

1、中断向量:

中断服务程序的入口地址。

2、请求中断

当某一中断源需要CPU为其进行中断服务时,就输出中断请求信号,使中断控制系统的中断请求触发器置位,向CPU请求中断。系统要求中断请求信号一直保持到CPU对其进行中断响应为止。

3、中断响应

CPU对系统内部中断源提出的中断请求必须响应,而且自动取得中断服务子程序的入口地址,执行中断 服务子程序。对于外部中断,CPU在执行当前指令的最后一个时钟周期去查询INTR引脚,若查询到中断请求信号有效,同时在系统开中断(即IF=1)的情 况下,CPU向发出中断请求的外设回送一个低电平有效的中断应答信号,作为对中断请求INTR的应答,系统自动进入中断响应周期。

4、保护现场

主程序和中断服务子程序都要使用CPU内部寄存器等资源,为使中断处理程序不破坏主程序中寄存器的内容,应先将断点处各寄存器的内容压入堆栈保护起来,再进入的中断处理。现场保护是由用户使用PUSH指令来实现的。

5、中断服务

中断服务是执行中断的主体部分,不同的中断请求,有各自不同的中断服务内容,需要根据中断源所要完成的功能,事先编写相应的中断服务子程序存入内存,等待中断请求响应后调用执行。

6、恢复现场

当中断处理完毕后,用户通过POP指令将保存在堆栈中的各个寄存器的内容弹出,即恢复主程序断点处寄存器的原值。

7、中断返回

在中断服务子程序的最后要安排一条中断返回指令IRET,执行该指令,系统自动将堆栈内保存的 IP/EIP和CS值弹出,从而恢复主程序断点处的地址值,同时还自动恢复标志寄存器FR或EFR的内容,使CPU转到被中断的程序中继续执行

8、中断嵌套

是指中断系统正在执行一个中断服务时,有另一个优先级更高的中断提出中断请求,这时会暂时终止当前正在执行的级别较低的中断源的服务程序,去处理级别更高的中断源,待处理完毕,再返回到被中断了的中断服务程序继续执行,这个过程就是中断嵌套。

七、进程与线程

1、进程是啥

是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位(进程是操作系统资源分配的最小单元。)

2、线程是啥

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

3、进程和线程的关系

本质区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。

包含关系:一个进程至少有一个线程,线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

资源开销:每个进程都有独立的地址空间,进程之间的切换会有较大的开销;线程可以看做轻量级的进程,同一个进程内的线程共享进程的地址空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。

影响关系:一个进程崩溃后,在保护模式下其他进程不会被影响,但是一个线程崩溃可能导致整个进程被操作系统杀掉,所以多进程要比多线程健壮。

4、其它概念

1.线程共享了进程哪些资源

2.进程间的通讯方式

八、二维数组和指针的用法

1、问题

A 、 *(a+i)[j],错误的原因是(a+i)结束后下一步运算是(a+i)[j],而不是*(a+i),正确写法是(*(a+i))[j].。B、正确。

C、正确写法:*(*(s+i)+j)。

D、正确写法和A一样。

2、知识学习

1.int a[][3]; 

  //二维数组可以不固定行,但是一定要固定列

2.int a[3][3];

a;//代表数组首行地址,一般用a[0][0]的地址表示
&a;//代表整个数组的地址,一般用a[0][0]地址表示
a[i];代表了第i行起始元素的地址(网上说是代表了第i行的地址,但我觉得不是,在讲数组与指针的关系时我会验证给大家看)
&a[i];代表了第i行的地址,一般用a[i][0]的地址表示
a[i]+j;//代表了第i行第j个元素地址,a[i]就是j==0的情况
a[i][j];//代表了第i行第j个元素
&a[i][j];//代表了第i行第j个元素的地址
*a;//代表数组a首元素地址也就是a[0]或者&a[0][0]
*(a+i);//代表了第i行首元素的地址,*a是i=0的情况
*(a+i)+j;//代表了第i行j个元素的地址
**a;//代表a的首元素的值也就是a[0][0]
*(*(a+i)+j);//代表了第i行第j个元素
#include <stdio.h>
int main()
{	 
	int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
	//输出各行首元素地址 
	printf("i\t&a[i]\ta+i\ta[i]\t&a[i][0]\n");
	for(int i=0;i<3;i++)
	printf("%d\t%d\t%d\t%d\t%d\t%d\n",i,&a[i],a+i,a[i],&a[i][0],*(a+i));
	//输出各行中列元素地址 
	printf("\n输出各行中列元素地址");
	printf("\n\ta[i]+i\t&a[i][i]\n") ;
	for(int i=0; i<3; i++)
	{
		printf("i=%d\t%d\t%d\n",i,a[i]+i,&a[i][i]);
	} 
	return 0;
} 

 从上图结果我们可以看出
    a+i == &a[i] == a[i] == &a[i][0] == *(a+i)//虽然它们的值相等,但他们的意义不全相等

    a+i和&a[i]意义相同都表示行地址,a[i],&a[i][0],*(a+i)意思相同,都表示该行起始元素地址。
 这里有个公式可以记一下:*(a+i) == a[i]; 加上个*号会等于指针变量去掉*号右边加个中括号,中括号内的值为指针变量所要增加的值,*(a+i)+j = a[i]+j;
      对于 *(*(a+i)+j) = a[i][j],我们可以先去掉最外面的*变成(*(a+i))[j],再去掉*为a[i][j]

九、函数传参过程

函数声明时候参数初始化 可以不传参

如果传参则覆盖 否则就是默认初始化值

十、什么是野指针

1、野指针的概念

野指针就是指针指向的位置不可知的。(随机的、不正确的、没有明确限制的)


2、野指针的三种情况

1.指针未定义,没有初始化

#include <stdio.h>
int main()
{
	int* p; //局部变量指针未初始化,默认就是随机值
	*p=10;
	return 0;
}

2.指针越界访问

#include <stdio.h>
int main()
{
	int arr[10]={0];
	int* p=arr;
	for(int i=0;i<12;i++)
	{
		*p++=i;
	}
	return 0;
}

3.指针指向的空间释放

int* test()
{
	int a=10;
	return &a;
}
int main()
{
	int* p=test();
	printf("%d\n",*p);
	return 0;
}

 3、如何规避野指针

1、指针初始化 (如果没有就初始化为NULL)
2、小心指针越界
3、指针指向空间释放即使其置为NULL
4、指针使用之前检查有效性 (例如:判断是否为NULL)

十一、c语言编译链接的过程

自我理解,需要把高级语言,编译成成机器能识别的语言,或者文本

1、预处理     

主要就是对标准库的处理,包含头文件,找到头文件的位置,比如输入输出流

还有就是对宏的处理,把宏写进程序中

gcc -E -o hello.i hello.c        .c文件变成.i文件
ggcc -E -o hello.i hello.c cc -E -o hello.i hello.c

2、编译

把.i文件变成汇编文件.s文件

gcc -S -o hello.s hello.i
就是把.cpp或者.c .py文件编译成汇编语言

对于语法错误,是在编译过程中发现的

3、汇编

把.s汇编文件变成.o目标文件,但是没有做链接

gcc -c -o hello.o hello.s

4、链接

把.o目标文件链接成一个APP文件,就是一个可执行的应用程序

5、 gcc -o test main.c sub.c


这个命令就包括了
1.预处理---编译---汇编---a.o目标文件
2.预处理---编译---汇编---b.o目标文件
最后a.o+b.o链接成为了应用程序

十二、构造函数和析构函数什么时候会被调用

1、构造和析构作用

对象的初始化和清理也是两个非常重要的安全问题。一个对象或者变量没有初始状态,对其使用后果是未知。同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

  c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

  对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是 空实现


2、构造函数

3、析构函数

十三、TCP/IP简单介绍

1、概念

TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTPSMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。(不够完整和通俗)

2、TCP和UDP的不同

1. 连接

  • TCP 是面向连接的传输层协议,传输数据前先要建立连接。
  • UDP 是不需要连接,即刻传输数据。

2. 服务对象

  • TCP 是一对一的两点服务,即一条连接只有两个端点。
  • UDP 支持一对一、一对多、多对多的交互通信

3. 可靠性

  • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。
  • UDP 是尽最大努力交付,不保证可靠交付数据。

4. 拥塞控制、流量控制

  • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
  • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。

5. 首部开销

  • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
  • UDP 首部只有 8 个字节,并且是固定不变的,开销较小。

6. 传输方式

  • TCP 是流式传输,没有边界,但保证顺序和可靠。
  • UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。

7. 分片不同

  • TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
  • UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。

TCP 和 UDP 应用场景:

由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:

  • FTP 文件传输;
  • HTTP / HTTPS;

由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:

  • 包总量较少的通信,如 DNSSNMP 等;
  • 视频、音频等多媒体通信
  • 广播通信;

TCP和Udp的区别是什么? - 知乎 (zhihu.com)

面试官:说说UDP和TCP的区别及应用场景 - 知乎 (zhihu.com)

十四、main函数之前进行了什么操作

main函数执行之前,主要就是初始化系统相关资源:

1.设置栈指针

2.初始化static静态和global全局变量,即data段的内容

3.将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容

4.运行全局构造器,估计是C++中构造函数之类的吧

5.将main函数的参数,argc,argv等传递给main函数,然后才真正运行main函数

这个可能问,单片机从上电到执行main中间经过啥?

main函数之前做了什么?_hjx5200的博客-优快云博客

十五、串口数据接收是怎么处理的

串口数据处理有两种方式:

1.通过串口中断接收到数据后,将数据放置环形缓存或队列中,待数据处理函数进行处理时进行处理。(RTOS)

2.通过轮询,时间片方式调度串口寄存器判断是否有数据,有数据则放入缓存待处理。(裸机)

十六、申请内存空间问题

 2

1)调用getMemory(str)后,str并未产生变化,依然是NULL(由于str是按值传递的)。只有形参p指向了一块新申请的空间。

2)程序运行到strcpy( str, "hello world" );处将产生错误

好像用二级指针才行

十七、运算符优先级 

  • 算术运算符>关系运算符>逻辑运算符

算关的

 C语言运算符优先级(超详细)_yuliying的博客-优快云博客

参考文章:

C语言常见面试题汇总_c语言面试题_Charles Ray的博客-优快云博客

c语言中static关键字用法详解_static在c语言中的用法_guotianqing的博客-优快云博客

C++ static静态成员变量详解 (biancheng.net)

C/C++程序内存的分配_c++内存分配_cherrydreamsover的博客-优快云博客

C语言:内存分配 - 知乎 (zhihu.com)

C语言的内存分配{静态内存&动态内存&堆栈}_c语言内存_梅尔文的博客-优快云博客

C/C++动态内存管理malloc/new、free/delete的异同_cherrydreamsover的博客-优快云博客

c++ 引用传参和指针传参的区别_wrdasj的博客-优快云博客

C++中指针和引用的区别(超详细)_引用 指针_WeiKangC的博客-优快云博客

线程与进程,你真得理解了吗_进程和线程的区别_云深i不知处的博客-优快云博客

进程和线程关系及区别_线程和进程的关系和区别_yaosiming2011的博客-优快云博客

二维数组与指针(详解)_二维数组指针_dddjjj-sicnu的博客-优快云博客

野指针概念、定义、及如何规避野指针_如何避免野指针?_ys.journey的博客-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值