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

嵌入式C/C++面试大全

基础语法

1.在main执⾏之前和之后执⾏的代码可能是什么?

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

  • 设置栈指针,其中栈存放的局部变量、函数参数、函数调用的返回地址
  • 初始化静态 static 变量和 global 全局变量,即 .data 段的内容
  • 将未初始化部分的全局变量赋初值:数值型 short , int , long 等为 0 , bool 为 FALSE ,指针为 NULL 等等,即 .bss 段的内容
  • 全局对象初始化,在 main 之前调⽤构造函数,这是可能会执⾏前的⼀些代码
  • 将main函数的参数 argc , argv 等传递给 main 函数,然后才真正运⾏ main 函数__attribute__((constructor))
    在这里插入图片描述
    main函数执⾏之后
  • 全局对象的析构函数会在main函数之后执⾏;
  • 可以⽤ atexit 注册⼀个函数,它会在main 之后执⾏;
  • attribute((destructor)(是GCC/Clang 的扩展语法,用于标记一个函数在 main()结束后或程序退出时自动执行。)

2、结构体内存对⻬问题?

  • 结构体内成员按照声明顺序存储,第⼀个成员地址和整个结构体地址相同。
  • 未特殊说明时,按结构体中size最⼤的成员对⻬(若有double成员,按8字节对⻬。)

注意:c++11以后引⼊两个关键字 alignas与 alignof。其中 alignof 可以计算出类型的对⻬⽅式, alignas 可以指定结构体的对⻬⽅式。

3、指针和引⽤的区别

  • 指针是⼀个变量,存储的是⼀个地址,引⽤跟原来的变量实质上是同⼀个东⻄,是原变量的别名
  • 指针可以有多级,引⽤只有⼀级
  • 指针可以为空,引⽤不能为NULL且在定义时必须初始化
  • 指针在初始化后可以改变指向,⽽引⽤在初始化之后不可再改变
  • sizeof指针得到的是本指针的⼤⼩, sizeof引⽤得到的是引⽤所指向变量的⼤⼩
  • 当把指针作为参数进⾏传递时,也是将实参的⼀个拷⻉传递给形参,两者指向的地址相同,但不是同⼀个变量,在函数中改变这个变量的指向不影响实参,⽽引⽤却可以。
  • 引⽤本质是⼀个指针,同样会占4字节内存;指针是具体变量,需要占⽤存储空间(,具体情况还要具体分析)。
  • 引⽤在声明时必须初始化为另⼀变量,⼀旦出现必须为typename refname &varname形式;指针声明和定义可以分开,可以先只声明指针变量⽽不初始化,等⽤到时再指向具体变量。
  • 引⽤⼀旦初始化之后就不可以再改变(变量可以被引⽤为多次,但引⽤只能作为⼀个变量引⽤);指针变量可以重新指向别的变量。
  • 不存在指向空值的引⽤,必须有具体实体;但是存在指向空值的指针。
void test(int *p)
{int a=1;p=&a;cout<<p<<" "<<*p<<endl;
}
int main(void)
{int *p=NULL;test(p);if(p==NULL)cout<<"指针p为NULL"<<endl;return 0;
}
//运⾏结果为:
//0x22ff44 1
//指针p为NULL
void testPTR(int* p) {int a = 12;p = &a;
}
void testREFF(int& p) {int a = 12;p = a;
}
void main()
{int a = 10;int* b = &a;testPTR(b);//改变指针指向,但是没改变指针的所指的内容cout << a << endl;// 10cout << *b << endl;// 10a = 10;testREFF(a);cout << a << endl;//12
}

注意:在编译器看来, int a = 10; int &b = a; 等价于 int * const b = &a; ⽽ b = 20; 等价于 *b = 20; ⾃动转换为指针和⾃动解引⽤.其中testREFF(a);编译器会​​自动将 x绑定到 ref​​,相当于:int& p = a; // 隐式发生在函数调用时

4、在传递函数参数时,什么时候该使用指针,什么时候该使用引用呢?

  • 需要返回函数内局部变量的内存的时候⽤指针。使⽤指针传参需要开辟内存,⽤完要记得释放指针,不然会内存泄漏。⽽返回局部变量的引⽤是没有意义的
// 正确:返回堆内存的指针(调用者需手动释放)
int* createArray(int size) {int* arr = new int[size];  // 堆内存,函数结束后仍存在for (int i = 0; i < size; i++) arr[i] = i;return arr;  // 返回指针
}int main() {int* ptr = createArray(5);// 使用 ptr...delete[] ptr;  // 必须手动释放!return 0;
}
  • 对栈空间⼤⼩⽐较敏感(⽐如递归)的时候使⽤引⽤。使⽤引⽤传递不需要创建临时变量,开销要更⼩
  • 类对象作为参数传递的时候使⽤引⽤,这是C++类对象传递的标准⽅式
class BigObject {// 假设类内有大量数据...
};// 正确:传递常量引用(避免拷贝)
void printObject(const BigObject& obj) {// 只读操作
}// 正确:传递引用(可修改对象)
void modifyObject(BigObject& obj) {// 修改 obj
}int main() {BigObject obj;printObject(obj);   // 无拷贝modifyObject(obj);  // 无拷贝return 0;
}

5、堆和栈的区别

栈空间默认是4M, 堆区⼀般是 1G - 4G
在这里插入图片描述

6、你觉得堆快⼀点还是栈快⼀点?

毫⽆疑问是栈快⼀点。
因为操作系统会在底层对栈提供⽀持,会分配专⻔的寄存器存放栈的地址,栈的⼊栈出栈操作也⼗分简单,并且有
专⻔的指令执⾏,所以栈的效率⽐较⾼也⽐较快。
⽽堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要⼀定的算法寻找合适⼤⼩的内存。并且获取堆的内
容需要两次访问,第⼀次访问指针,第⼆次根据指针保存的地址访问内存,因此堆⽐较慢。

7、区别以下指针类型?

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
  • int *p[10]表示指针数组,强调数组概念,是⼀个数组变量,数组⼤⼩为10,数组内每个元素都是指向int类型的指针变量。
  • int (*p)[10]表示数组指针,强调是指针,只有⼀个变量,是指针类型,不过指向的是⼀个int类型的数组,这个数组⼤⼩是10。
  • int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
  • int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

8、new / delete 与 malloc / free的异同

相同点:

  • 都可⽤于内存的动态申请和释放
    不同点:
  • 前者是C++运算符,后者是C/C++语⾔标准库函数
  • new⾃动计算要分配的空间⼤⼩, malloc需要⼿⼯计算
  • new是类型安全的, malloc不是。例如:
int *p = new float[2]; //编译错误
int *p = (int*)malloc(2 * sizeof(double));//编译⽆错误
  • new调⽤名为operator new的标准库函数分配⾜够空间并调⽤相关对象的构造函数, delete对指针所指对象运⾏适当的析构函数;然后通过调⽤名为operator delete的标准库函数释放该对象所⽤内存。后者free均没有相关调⽤,可能会造成内存泄漏(如果对象内部有动态分配的资源(如 new int[100]),free不会调用析构函数)
  • 后者需要库⽂件⽀持,前者不⽤
  • new是封装了malloc,直接free不会报错,但是这只是释放内存,⽽不会析构对象

9、new和delete是如何实现的?

  • new的实现过程是:⾸先调⽤名为operator new的标准库函数,分配⾜够⼤的原始为类型化的内存,以保存指定类型的⼀个对象;接下来运⾏该类型的⼀个构造函数,⽤指定初始化构造对象;最后返回指向新分配并构造后的的对象的指针
  • delete的实现过程:对指针指向的对象运⾏适当的析构函数;然后通过调⽤名为operator delete的标准库函数释放该对象所⽤内存

10、malloc和new的区别?

  • malloc和free是标准库函数,⽀持覆盖; new和delete是运算符,不重载。
  • malloc仅仅分配内存空间, free仅仅回收空间,不具备调⽤构造函数和析构函数功能,⽤malloc分配空间存储类的对象存在⻛险; new和delete除了分配回收功能外,还会调⽤构造函数和析构函数。
  • malloc和free返回的是void类型指针(必须进⾏类型转换), new和delete返回的是具体类型指针。

11、既然有了malloc/free, C++中为什么还需要new/delete呢?直接⽤malloc/free不好吗

  • malloc/free和new/delete都是⽤来申请内存和回收内存的。
    在对⾮基本数据类型的对象使⽤的时候,对象创建的时候还需要执⾏构造函数,销毁的时候要执⾏析构函数。
  • ⽽malloc/free是库函数,是已经编译的代码,所以不能把构造函数和析构函数的功能强加给malloc/free,所
    以new/delete是必不可少的。

12、被free回收的内存是⽴即返还给操作系统吗?

不是的,被free回收的内存会⾸先被ptmalloc使⽤双链表保存起来,当⽤户下⼀次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调⽤,占⽤过多的系统资源。同时ptmalloc也会尝试对⼩块内存进⾏合并,避免过多的内存碎⽚。

13、宏定义和函数有何区别?

  • 宏在编译时完成替换,之后被替换的⽂本参与编译,相当于直接插⼊了代码,运⾏时不存在函数调⽤,执⾏起来更快;函数调⽤在运⾏时需要跳转到具体调⽤函数。
    *宏定义属于在结构中插⼊代码,没有返回值;函数调⽤具有返回值。
  • 宏定义参数没有类型,不进⾏类型检查;函数参数具有类型,需要检查类型。
  • 宏定义不要在最后加分号。

14、宏定义和typedef区别?

  • 宏主要⽤于定义常量及书写复杂的内容; typedef主要⽤于定义类型别名。
  • 宏替换发⽣在编译阶段之前,属于⽂本插⼊替换; typedef是编译的⼀部分。
  • 宏不检查类型; typedef会检查数据类型。
  • 宏不是语句,不在在最后加分号; typedef是语句,要加分号标识结束。
  • 注意对指针的操作, typedef char * p_char和#define p_char char *区别巨⼤。
#define p_char char*   // p_char 是宏,替换为 char*int main() {p_char p1, p2;     // 替换后:char* p1, p2;char a = 'A';p1 = &a;           // p1 是 char*p2 = &a;           // 错误!p2 是 char(不是指针)return 0;
}

15、变量声明和定义区别?

  • 声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地⽅为其分配存储空间。
  • 相同变量可以在多处声明(外部变量extern),但只能在⼀处定义。

16、strlen和sizeof区别?

  • sizeof是运算符,并不是函数,结果在编译时得到⽽⾮运⾏中获得; strlen是字符处理的库函数。
  • sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化); strlen的参数只能是字符指针且结尾是’\0’的字符串。
  • 因为sizeof值在编译时确定,所以不能⽤来得到动态分配(运⾏时分配)存储空间的⼤⼩。
int main(int argc, char const *argv[]){const char* str = "name";sizeof(str); // 取的是指针str的⻓度,是8strlen(str); // 取的是这个字符串的⻓度,不包含结尾的 \0。⼤⼩是4return 0;
}

17、常量指针和指针常量区别?

  • 指针常量是⼀个指针,读成常量的指针,指向⼀个只读变量,也就是后⾯所指明的int const 和 const int,都是⼀个常量,可以写作int const *p或const int *p。
  • 常量指针是⼀个不能给改变指向的指针。指针是个常量,必须初始化,⼀旦初始化完成,它的值(也就是存放在指针中的地址)就不能在改变了,即不能中途改变指向,如int *const p。

18、a和&a有什么区别?

假设数组int a[10]; int (*p)[10] = &a;其中:

  • a是数组名,是数组⾸元素地址, +1表示地址值加上⼀个int类型的⼤⼩,如果a的值是0x00000001,加1操作
    后变为0x00000005。 *(a + 1) = a[1]。
  • &a是数组的指针,其类型为int (*)[10](就是前⾯提到的数组指针),其加1时,系统会认为是数组⾸地址加
    上整个数组的偏移(10个int型变量),值为数组a尾元素后⼀个元素的地址。
  • 若(int *)p ,此时输出 *p时,其值为a[0]的值,因为被转为int *类型,解引⽤时按照int类型⼤⼩来读取。

19、C++和C语⾔的区别

  • C++中new和delete是对内存分配的运算符,取代了C中的malloc和free。
  • 标准C++中的字符串类取代了标准C函数库头⽂件中的字符数组处理函数(C中没有字符串类型)。
  • C++中⽤来做控制态输⼊输出的iostream类库替代了标准C中的stdio函数库。
  • C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。C++ 的 try-catch是一种​​结构化异常处理(Structured Exception Handling, SEH)​​机制,用于在运行时检测和处理错误。它的核心原理是:​​当 try块中的代码抛出异常时,程序立即跳转到匹配的 catch块,并跳过 try块剩余代码​​。
  • 在C++中,允许有相同的函数名,不过它们的参数类型不能完全相同,这样这些函数就可以相互区别开来。⽽
    这在C语⾔中是不允许的。也就是C++可以重载, C语⾔不允许。
  • C++语⾔中,允许变量定义语句在程序中的任何地⽅,只要在是使⽤它之前就可以;⽽C语⾔中,必须要在函
    数开头部分。⽽且C++不允许重复定义变量, C语⾔也是做不到这⼀点的
C(C89标准):要求变量定义必须集中在块的开头(所有定义必须在第一个可执行语句之前)。例如:
void func() {int a;  // 必须在开头a = 10;int b;  // 这样在C89中是错误的
}
  • 在C++中,除了值和指针之外,新增了引⽤。引⽤型变量是其他变量的⼀个别名,我们可以认为他们只是名字
    不相同,其他都是相同的。

20、C++中struct和class的区别

相同点:

  • 两者都拥有成员函数、公有和私有部分
  • 任何可以使⽤class完成的⼯作,同样可以使⽤struct完成

不同点:

  • 两者中如果不对成员不指定公私有, struct默认是公有的, class则默认是私有的
  • class默认是private继承,⽽struct模式是public继承

21、define宏定义和const的区别

编译阶段:

  • define是在编译的预处理阶段起作⽤,⽽const是在编译、运⾏的时候起作⽤

安全性:

  • define只做替换,不做类型检查和计算,也不求解,容易产⽣错误,⼀般最好加上⼀个⼤括号包含住全部的内
    容,要不然很容易出错
  • const常量有数据类型,编译器可以对其进⾏类型安全检查

内存占⽤:

  • define只是将宏名称进⾏替换,在内存中会产⽣多分相同的备份。 const在程序运⾏中只有⼀份备份,且可以
  • 执⾏常量折叠,能将复杂的的表达式计算出结果放⼊常量表
  • 宏替换发⽣在编译阶段之前,属于⽂本插⼊替换; const作⽤发⽣于编译过程中。
  • 宏不检查类型; const会检查数据类型。
  • 宏定义的数据没有分配内存空间,只是插⼊替换掉; const定义的变量只是值不能改变,但要分配内存空间。

22、C++中const和static的作⽤

static:

  • 不考虑类的情况
    • 隐藏。所有不加static的全局变量和函数具有全局可⻅性,可以在其他⽂件中使⽤,加了之后只能
      在该⽂件所在的编译模块中使⽤
    • 默认初始化为0,包括未初始化的全局静态变量与局部静态变量,都存在全局未初始化区
    • 静态变量在函数内定义,始终存在,且只进⾏⼀次初始化,具有记忆性,其作⽤范围与局部变量相同,函数退出后仍然存在,但不能使⽤
  • 考虑类的情况
    • static成员变量:只与类关联,不与类的对象关联。定义时要分配空间,不能在类声明中初始化,必须在类定义体外部初始化,初始化时不需要标示为static;可以被⾮static成员函数任意访问。
    • static成员函数:不具有this指针,⽆法访问类对象的⾮static成员变量和⾮static成员函数; 不能被
      声明为const、虚函数和volatile;可以被⾮static成员函数任意访问
//static成员变量例子
#include <iostream>class Counter {
public:static int count; // 声明 static 成员变量Counter() { count++; }static void printCount() { std::cout << "Count: " << count << std::endl; }
};int Counter::count = 0; // 必须在类外初始化(C++17 前)int main() {Counter c1, c2, c3;Counter::printCount(); // 输出 Count: 3return 0;
}
//static成员函数例子
#include <iostream>class MathUtils {
public:static int add(int a, int b) { return a + b; } // static 成员函数void normalFunc() {std::cout << add(2, 3) << std::endl; // 可以调用 static 函数}
};int main() {std::cout << MathUtils::add(5, 7) << std::endl; // 直接通过类名调用MathUtils obj;obj.normalFunc(); // 输出 5return 0;
}

const:

  • 不考虑类的情况
    • const常量在定义时必须初始化,之后⽆法更改
    • const形参可以接收const和非const类型的实参,例如// i 可以是 int 型或者 const int 型void fun(const int& i){ //…}
  • 考虑类的情况
    • const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进⾏初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化
    • const成员函数: const对象不可以调⽤非const成员函数;非const对象都可以调用;不可以改变非mutable(⽤该关键字声明的变量可以在const成员函数中被修改)数据的值
//const成员函数
#include <iostream>class Student {
public:void setName(std::string name) { m_name = name; } // 非 const 函数std::string getName() const { return m_name; }    // const 函数private:std::string m_name;
};int main() {const Student s1; // const 对象// s1.setName("Alice"); // 错误!const 对象不能调用非 const 函数std::cout << s1.getName() << std::endl; // 正确,调用 const 函数Student s2; // 非 const 对象s2.setName("Bob"); // 正确std::cout << s2.getName() << std::endl; // 正确return 0;
}

23、C++的顶层const和底层const

  • 概念区分
    • 顶层const:指的是const修饰的变量本身是⼀个常量,⽆法修改,指的是指针,就是 * 号的右边
    • 底层const:指的是const修饰的变量所指向的对象是⼀个常量,指的是所指变量,就是 * 号的左边
  • 举个例⼦
int a = 10;int* const b1 = &a; //顶层const, b1本身是⼀个常量
const int* b2 = &a; //底层const, b2本身可变,所指的对象是常量
const int b3 = 20; //顶层const, b3是常量不可变
const int* const b4 = &a; //前⼀个const为底层,后⼀个为顶层, b4不可变
const int& b5 = a; //⽤于声明引⽤变量,都是底层const

24、数组名和指针(这⾥为指向数组⾸元素的指针)区别?

  • ⼆者均可通过增减偏移量来访问数组中的元素。
  • 数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有⾃增、⾃减等操作。
  • 当数组名当做形参传递给调⽤函数后,就失去了原有特性,退化成⼀般指针,多了⾃增、⾃减操作,但sizeof运算符不能再得到原数组的⼤⼩了。
数组名会退化为指向首元素的指针​​:
void func(int arr[]) {  // 等价于 int *arr// 此时 arr 是一个指针,不是数组
}
​​sizeof(arr)返回的是指针的大小​​(通常是 4或 8字节),而不是数组的大小。

25、拷⻉初始化和直接初始化

  • 当⽤于类类型对象时,初始化的拷⻉形式和直接形式有所不同:直接初始化直接调⽤与实参匹配的构造函数,
    拷⻉初始化总是调⽤拷⻉构造函数。拷⻉初始化⾸先使⽤指定构造函数创建⼀个临时对象,然后⽤拷⻉构造函
    数将那个临时对象拷⻉到正在创建的对象。举例如下:
string str1("I am a string");//语句1 直接初始化
string str2(str1);//语句2 直接初始化, str1是已经存在的对象,直接调⽤拷⻉构造函数对str2进⾏初始化
string str3 = "I am a string";//语句3 拷⻉初始化,先为字符串”I am a string“创建临时对象,再把临
时对象作为参数,使⽤拷⻉构造函数构造str3
string str4 = str1;//语句4 拷⻉初始化,这⾥相当于隐式调⽤拷⻉构造函数,⽽不是调⽤赋值运算符函数
  • 为了提⾼效率,允许编译器跳过创建临时对象这⼀步, 直接调⽤构造函数构造要创建的对象,这样就完全等价
    于直接初始化了(语句1和语句3等价)

26、初始化和赋值的区别

  • 对于简单类型来说,初始化和赋值没什么区别
  • 对于类和复杂数据类型来说,这两者的区别就⼤了,举例如下:
http://www.dtcms.com/a/331394.html

相关文章:

  • 传统自然语言处理任务入口
  • css预编译器实现星空背景图
  • XJar 加密 jar 包
  • Vscode的wsl环境开发ESP32S3的一些问题总结
  • 《贵州棒球百科》体育赛事排名·棒球1号位
  • 建造者模式C++
  • 串口通信中,实现串口接收函数时,避免数据丢失或被覆盖的方法
  • 20250814在荣品RD-RK3588开发板的Android13下解决卡迪的LCD屏在开机的时候brightness最暗【背光的pwm信号的极性反了】
  • 机器学习核心概念与实践笔记
  • 安卓设备通过USB,连接继电器,再通过继电器开关闸机
  • 前端包管理工具
  • 【FreeRTOS】任务管理:创建与删除任务,任务优先级与阻塞
  • 计算机网络---传输控制协议Transmission Control Protocol(TCP)
  • Redis的 ​​散列(Hash)​​ 和 ​​列表(List)​​ 数据结构操作详解
  • 力扣-64.最小路径和
  • 【AI推理部署教程】使用 vLLM 运行智谱 GLM-4.5V 视觉语言模型推理服务
  • 电商双 11 美妆数据分析总结(补充)
  • 入门概述(面试常问)
  • 中久数创——笔试题
  • Android构建工具版本兼容性对照表
  • Git 中切换到指定 tag
  • 会议系统核心流程详解:创建、加入与消息交互
  • 卫星通信链路预算之七:上行载噪比计算
  • MySQL-dble分库分表方案
  • 【最新版】怎么下载mysqlclient并成功安装?
  • 物化视图优先迁移大表,缩短逻辑迁移时间
  • MySql——binlog和redolog的区别
  • uniapp开发动态添加密码验证
  • Go语言全面解析:从入门到精通
  • C/C++ 指针与内存操作详解——从一级指针到字符串转换函数的完整解析