这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【转载】嵌入式工程师常见面试题(三)--from文

共2条 1/1 1 跳转至

【转载】嵌入式工程师常见面试题(三)--from文

工程师
2025-02-18 14:02:48     打赏

1、new 和 malloc 的区别?

new 和 malloc 都是用于动态分配内存的方法,但是它们在以下方面存在一些区别:

内存分配位置:new 操作从自由存储区为对象动态分配内存空间,而 malloc 函数从堆上动态分配内存。自由存储区不仅可以是堆,还可以是静态存储区,这取决于 new 在哪里为对象分配内存。

返回类型安全性:new 操作符内存分配成功时,返回的是对象类型的指针,与对象类型严格匹配,无需进行类型转换,因此 new 是符合类型安全性的操作符。而 malloc 函数在内存分配成功后返回的是 void*,需要通过强制类型转换将 void* 指针转换成所需类型。

内存分配失败时的返回值:当 new 内存分配失败时,会抛出 bac_alloc 异常,不会返回 NULL;而 malloc 内存分配失败时,返回 NULL。

是否需要指定内存大小:使用new申请内存时,不需要指定内存块的大小,编译器会根据类型信息自行计算;而malloc则需要显式地指出所需内存块的大小。

总的来说,new和malloc在内存分配位置、返回类型安全性、内存分配失败时的返回值以及是否需要指定内存大小等方面存在差异。

2、malloc的底层实现

(1)当开辟的空间小于 128K 时,调用 brk()函数,malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata(此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址)

(2)当开辟的空间大于 128K 时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟。

3、 在1G内存的计算机中能否malloc(1.2G)?为什么?

在1G内存的计算机中,无法使用malloc(1.2G)。

在C语言中,malloc()函数用于动态分配内存。当使用malloc()请求超过可用内存的大小时,会发生两种情况:

分配失败:如果请求的内存大小超过了操作系统可用的内存大小,malloc()将返回NULL,表示分配失败。在这种情况下,程序将无法继续执行。

分配成功但导致崩溃:如果请求的内存大小超过了可用的内存大小,但malloc()仍然返回了非NULL的指针,这通常是由于操作系统使用了内存映射技术或虚拟内存机制。这种情况下,程序可能会继续执行,但它可能会遇到未定义的行为,例如访问已分配的内存时崩溃或导致性能下降。

在1G内存的计算机中,由于可用内存只有1GB,因此无法使用malloc(1.2G),因为这将请求超过可用内存大小的内存大小。如果尝试这样做,malloc()将返回NULL,并且程序将无法继续执行。

4、指针与引用的相同和区别;如何相互转换?

引用时C++独有的特性,而指针则是C/C++都有的,它们有一些相似之处,但也有很多区别。

相同点:

它们都是用来间接访问内存的方式。

它们本身都占用内存(指针占用内存来存储所指向对象的地址,引用则是对象的别名,所以其实引用本身也是对象)。

它们都可以用来访问动态分配的内存(使用malloc()等函数)。

不同点:

指针是一个实体,它存储的是一个地址,指向内存的一个存储单元。而引用是原变量的一个别名,它存储的是原变量的值。

指针可以被重新赋值,指向不同的对象,而引用在声明后就不能改变其指向的对象。

指针可以被const修饰,而引用不能被const修饰。

sizeof运算符在操作引用时得到的是对象本身的大小,而操作指针时得到的是指针变量本身的大小。

相互转换:

指针转引用:将指针变量的值直接赋值给引用变量即可。

引用转指针:可以使用类型转换操作符(*)将引用转换为指针。

总的来说,指针和引用在功能和使用上有一些区别,需要根据具体情况选择使用哪种方式。

5、extern"C”的作用

extern "C"是一个C语言链接器的关键字,它有以下几个作用:

使得在C++中使用C编译方式成为可能,指明该函数使用C编译方式。

在某些情况下,使用extern "C"声明函数,可以使得C++编译器按照C语言的方式对函数进行编译和链接,从而能够正确地调用该函数。

extern "C"可以用于在C++中调用由C语言编写的库函数。因为C++与C语言的函数调用方式不同,使用extern "C"可以指明该函数使用C语言的方式进行链接,避免因为链接方式不同而导致的错误。

在使用动态链接库(DLL)时,使用extern "C"可以保证函数的导出和导入与C语言一致。

总之,extern "C"的作用是为了在C++中使用C语言的编译和链接方式,并且在一些特定情况下保证函数的正确导出和导入。

6、重写memcpy()函数需要注意哪些问题,(strcat strncat strcmp strcpy)那些函数会导致内存溢出?

重写 memcpy() 函数时需要注意以下问题:

源地址和目标地址的正确性:确保源地址和目标地址是有效的、合法的内存地址。在复制过程中,如果源地址无效或目标地址不足,会导致错误或未定义的行为。

复制的字节数:确保复制的字节数不超过目标地址的剩余空间,以避免内存溢出。在复制过程中,如果目标地址的剩余空间不足以容纳要复制的数据,会导致内存溢出。

目标地址的内存分配:如果目标地址是动态分配的内存区域(例如通过 malloc() 函数分配的内存),则在复制之前需要确保目标地址的内存已经分配,并且在复制完成后及时释放,以避免内存泄漏。

边界检查和错误处理:对于类似于 strcat()、strncat()、strcmp()、strcpy() 等函数,在处理字符串时需要注意边界检查,确保输入的字符串不会超过目标地址的限制。对于错误的输入,应该有适当的错误处理机制,例如报错或者返回错误码。

下面是关于 strcat()、strncat()、strcmp()、strcpy() 函数的一些注意事项:

strcat() 函数用于将一个字符串追加到另一个字符串的末尾。如果目标字符串的长度不足,或者目标字符串的剩余空间不足以容纳要追加的数据,就会导致内存溢出。

strncat() 函数类似于 strcat(),但是可以指定要复制的字节数。在使用 strncat() 时,需要确保目标字符串的剩余空间足够大,以避免内存溢出。

strcmp() 函数用于比较两个字符串是否相等。这个函数不会导致内存溢出,但是需要注意输入的字符串长度,如果输入的字符串过长可能会导致性能问题。

strcpy() 函数用于将一个字符串复制到另一个字符串中。在使用 strcpy() 时,需要确保目标字符串的长度足够大,以避免内存溢出。此外,还需要注意源字符串是否具有足够的空间,以避免访问越界的问题。

7、char 和 int 之间的转换

在C/C++中,可以使用以下方式将char和int之间进行转换:

将char转换为int:

使用强制类型转换,将char的ASCII码值转换为int。例如,对于char c = 'A';,可以使用int i = (int)c;来将c转换为int类型的i。

如果char类型的数据只包含ASCII码值,可以使用自然的隐式转换。例如,对于char c = 'A';,可以使用int i = c;来将c转换为int类型的i。

将int转换为char:

如果int类型的数据是ASCII码值,可以直接赋值给char类型的变量。例如,对于int i = 65;,可以使用char c = i;来将i转换为char类型的c。

如果int类型的数据不是ASCII

8、static的用法(定义和用途) static静态变量,只初始化一次

在编程中,static 是一个关键字,用于指明变量或函数的特性。在C语言中,static 可以用于以下两种情况:

定义静态变量:

静态变量是在程序执行期间只初始化一次的变量。它们在程序开始时被分配内存,并在程序结束时被释放。

静态变量默认值为0,但也可以在定义时显式地初始化。

静态变量可以用于记录某些状态或计数器,因为它们不会在程序运行期间被重新初始化。

定义静态函数:

静态函数是在程序执行期间只初始化一次的函数。它们在程序开始时被分配内存,并在程序结束时被释放。

静态函数只在其定义的模块中被使用,不能被其他模块直接调用。

静态函数可以用于实现某些特定的功能,例如加密、解密、字符串处理等。

9、const的用法(定义和用途)

告诉编译器该变量的值不能被修改:

使用 const 关键字定义变量可以明确告知编译器该变量的值不可被修改,这有助于编译器进行类型检查和代码优化。

避免不必要的内存分配:

使用 const 关键字定义变量可以避免不必要的内存分配,因为编译器会将这些常变量存储在符号表中,而不是在内存中。

在函数中使用:

在函数中使用 const 关键字可以确保函数不会修改传入参数的值,这有助于提高函数的可读性和安全性。同时,函数也可以返回 const 类型的值,以确保函数返回的值不会被修改。

10、const 常量和 #define 的区别(编译阶段、安全性、内存占用等)

const 常量和#define 都是在C/C++中用于定义常量,但它们之间有一些重要的区别。

定义方式:

const 关键字用于定义常量,它是类型安全的,可以用于声明各种类型的常量,如整数、浮点数、字符等。而#define 是通过预处理器定义的,它可以定义任何类型的常量,包括字符串和其他复杂的表达式。

编译阶段:

const 是在编译阶段处理的,它在编译时被解析为常量表达式,并且可以直接内联到代码中。而#define 是由预处理器在编译预处理阶段处理的,它会被替换为相应的常量表达式。

类型安全:

const 关键字是类型安全的,它需要在声明时指定类型,并且在编译时进行类型检查。而#define 没有类型检查,它只是一个简单的文本替换,没有类型检查和语法检查。

内存占用:

const 常量是静态分配内存的,它们被存储在程序的数据区中,并且在编译时就已经确定了其值。而#define 只是一个符号,在编译预处理阶段被替换为相应的值,因此它不会分配内存,只是在编译时进行了一次文本替换。

可读性和安全性:

const 常量具有更好的可读性和安全性,它们可以提供更明确的语义,告诉读者该值是不能被修改的。而#define 只是一个预处理器指令,容易被误用或滥用,因此它的可读性和安全性相对较低。


综上所述,const 常量提供了更好的类型安全性和可读性,并且在编译时进行内存分配,同时也具有更好的性能。而#define 则更灵活,可以在编译预处理阶段进行一些复杂的操作,但它的类型安全性和可读性相对较低。在实际编程中,应根据具体情况选择使用 const 常量还是 #define。


11、 volatile作用和用法


volatile 是一个修饰符,用于声明在多线程环境下可能会被意外修改的变量。


在多线程环境下,由于有多个线程同时访问共享变量,可能会导致一些意外的行为,如一个线程正在修改变量时,另一个线程正在读取该变量,读取到的值可能不是最新的值。这种情况下,就需要使用 volatile 关键字来确保变量的可见性和一致性。


具体来说,使用 volatile 关键字可以保证以下几点:


保证变量的可见性:当一个线程修改变量的值时,其他线程会立即看到这个最新的值。


防止指令重排:由于编译器可能会对代码进行优化,导致一些指令的执行顺序与代码中的顺序不一致。使用 volatile 关键字可以防止这种指令重排,确保代码的执行顺序与代码中的顺序一致。


需要注意的是,虽然使用 volatile 关键字可以保证变量的可见性和一致性,但它不能保证线程之间的同步。如果需要保证线程之间的同步,还需要使用其他的同步机制。


12、变量的作用域(全局变量和局部变量)


变量的作用域是指变量在程序中可以使用的范围。在C/C++中,变量可以分为全局变量和局部变量两种。


全局变量(global variable):

全局变量是在函数外部定义的变量,它们的作用域是整个程序,可以从头文件一直使用到程序结束。全局变量通常在程序启动时初始化,并且可以被程序中的多个函数共同使用。

局部变量(local variable):

局部变量是在函数内部定义的变量,它们的作用域仅限于函数内部。当函数执行结束时,局部变量会被销毁,其内存空间也会被释放。

13、sizeof 与 strlen (字符串,数组)


sizeof() 是 C/C++ 中的运算符,用于获取变量或数据类型在内存中所占用的字节数。而 strlen() 是 C/C++ 中的函数,用于计算字符串的长度(不包括字符串结束符 '\0')。


对于字符串和数组,sizeof() 和 strlen() 的使用有以下区别:


字符串:


sizeof() 运算符用于获取整个字符串占用的字节数,包括字符串结束符 '\0'。例如,对于 char str[] = "hello";,sizeof(str) 的结果是 6(包括 '\0')。

strlen() 函数用于获取字符串的长度,即从字符串的起始地址开始,直到遇到结束符 '\0' 前,所经过的字节数。例如,对于 char str[] = "hello";,strlen(str) 的结果是 5。

数组:


sizeof() 运算符用于获取整个数组占用的字节数。例如,对于 int arr[] = {1, 2, 3};,sizeof(arr) 的结果是 sizeof(int) * 数组长度(即 sizeof(int) * 3)。

strlen() 函数通常不用于数组,因为数组没有结束符 '\0',无法计算其长度。

需要注意的是,sizeof() 运算符的结果是一个编译时确定的常量表达式,而 strlen() 函数需要在运行时逐个字符地计算字符串的长度。在使用时需要根据具体情况选择合适的函数,并注意避免越界访问和空指针异常等问题。


14、经典的sizeof(struct)和内存对齐(一字节对齐) 


在C语言中,sizeof()运算符用于获取变量或数据类型的大小。对于struct类型,sizeof()运算符将返回整个struct的大小,而不是每个成员变量的大小之和。这是因为C语言中的struct成员变量的对齐方式可能会导致struct的大小不是成员变量大小的整数倍。


内存对齐是为了提高内存访问的效率而引入的一种内存布局技术。在32位系统中最小的对齐单位是4字节,在64位系统中最小的对齐单位是8字节。根据对齐单位,编译器会在变量或结构体成员之间插入填充字节,使得它们的起始地址满足对齐要求。


对于一字节对齐,它通常用于一些特定的数据类型,例如char类型,它占用的空间就是1个字节。对于一些需要按照字节进行访问的数据类型,例如位域(bit-fields),也需要使用一字节对齐来保证数据的正确性。


在使用sizeof()运算符时,需要注意以下几点:


对于结构体或联合体类型,sizeof()返回的大小是整个结构体或联合体的占据的内存空间,而不是每个成员变量的大小之和。

在进行内存对齐时,需要考虑结构体成员变量的类型、大小和顺序,以及编译器和系统的对齐要求。合理地规划内存布局可以提高程序的性能和效率。

对于一些特殊的对齐要求,例如一字节对齐,需要使用特定的语法或技巧来实现,例如使用#pragma pack指令或者在结构体中使用特定的对齐修饰符(如__attribute__((packed))。

总之,sizeof()运算符和内存对齐是C语言中重要的概念,需要熟悉和掌握它们的使用方法和注意事项,以便更好地管理内存和提高程序的性能和效率。


推荐博客地址:http://t.csdn.cn/sAlrc


15、const * char 与const char * 


"const * char" 和 "const char *" 都是C语言中的指针声明,但它们有一些不同之处。


"const * char":


这个声明表示一个指向常量字符的指针。这里的"const"修饰的是指针所指向的数据,即字符数据是不可修改的,而指针本身是可修改的。

可以用作指向字符数组的首地址,或者指向字符串的地址。

"const char *":


这个声明表示一个指向字符常量的指针。这里的"const"修饰的是指针本身,即指针自身是

不可修改的,而指针所指向的数据是可修改的。

通常用于指向字符串字面值或字符常量。

总结来说,"const * char"表示一个指向常量字符的指针,而"const char *"表示一个指向字符常量的指针。在C语言中,对于字符串字面值或字符常量,通常使用"const char *"来声明指针。


16、inline函数


inline 是一个C/C++中的关键字,用于告诉编译器该函数的实现应该在编译时进行内联。内联函数的目的是为了减少程序的运行时开销,通过在编译时将函数的调用替换为函数的实现,避免了函数调用的额外开销,提高了程序的执行效率。


17、内存四区,什么变量分别存储在什么区域,堆上还是栈上。


在执行一个C/C++语言程序时,会将程序分配到的内存分为四个区域:栈区、堆区、全局区(静态区)和代码区。每个程序都有唯一的四个内存区域,我们需要熟悉和了解各个区域的特性,例如存储什么类型的数据,有谁去申请开辟,又有谁去管理释放。

栈区:栈区是用来存储函数调用和局部变量的一块内存区域,它是在程序执行时自动分配的,并且只在函数调用时使用,不会造成内存泄漏。在函数返回后,栈区会自动释放。

堆区:堆区是用来存储动态分配的内存的一块内存区域,它是由程序员分配和释放的。在程序执行时,通过malloc等函数从堆区分配一块内存,使用完后需要手动释放,否则会造成内存泄漏。

全局区(静态区):全局区是用来存储全局变量和静态变量的一块内存区域,它是在程序执行时就已经分配好并且一直存在直到程序结束时才被释放。

代码区:代码区是用来存储程序代码的一块内存区域,它是在程序执行时就已经分配好并且一直存在直到程序结束时才被释放。

需要注意的是,这四个区域是按照顺序依次排列的,并且每个区域的存储数据类型和申请方式都有所不同,需要根据具体情况进行管理和使用。

来源: 整理文章为传播相关技术,网络版权归原作者所有,如有侵权,请联系删除。


                            



专家
2025-02-18 20:31:19     打赏
2楼

学习一下


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]