嵌入式基础二——C/C++

二、C/C++

2.1 c和c++区别、概念相关面试题

2.1.1 new和malloc的区别

属性:

new/delete是C++关键字,需要编译器支持。

malloc/free是库函数,需要头文件支持。

特征:

在这里插入图片描述

2.1.2 malloc的底层实现

Linux维护一个break指针,这个指针指向堆空间的某个地址。从堆起始地址到break之间的地址空间为映射好的,可以供进程访问;而从break往上,是未映射的地址空间,如果访问这段空间则程序会报错。我们用malloc进行内存分配就是从break往上进行的。 获得了break指针的位置也就得到了堆内存申请的起始地址。

malloc实际上是将可用空间用一个空闲链表连接起来,若用户申请空间,就遍历该链表,找到第一个满足条件的空闲块,将其进行拆分,返回合适大小的空间给用户,将剩下的部分链接到链表中。当调用free释放空间时,会把这块空间连接到空闲链表上。到最后,该空闲链就会被切成很多的小内存块,一旦用户申请一块较大的空间,空闲链中的空间大小都无法满足需求,malloc会申请延时,对空闲链表进行检查,内存重新整理,把相邻的小片段合并成大的空闲块。

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

malloc能够申请的空间大小与物理内存的大小没有直接关系,仅与程序的虚拟地址空间相关。程序运行时,堆空间只是程序向操作系统申请划出来的一大块虚拟地址空间。应用程序通过malloc申请空间,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的。

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

共性:

1)都是地址的概念,指针指向某一内存、它的内容是所指内存的地址;引用则是某块内存的别名。

2)从内存分配上看:两者都占内存,程序为指针会分配内存,一般是4个字节;而引用的本质是指针常量,指向对象不能变,但指向对象的值可以变。两者都是地址概念,所以本身都会占用内存。

区别:

1)引用必须被初始化,指针不必。

2)引用初始化以后不能被改变,指针可以改变所指的对象。

3)不存在指向空值的引用,但是存在指向空值的指针。

转换:

指针转引用:把指针用*就可以转换成对象,可以用在引用参数当中。

引用转指针:把引用类型的对象用&取地址就获得指针了。

int a = 5;

int *p = &a;

void fun(int &x){}//此时调用fun可使用 : fun(*p);

//p是指针,加个*号后可以转换成该指针指向的对象,此时fun的形参是一个引用值,

//p指针指向的对象会转换成引用X。

注:没有指向引用的指针,因为引用是没有地址的,但是有指针的引用,因为引用不是对象,没有地址

2.1.5 C语言检索内存情况 内存分配的方式

在这里插入图片描述

一个程序本质上都是由BSS段、data段、text三个组成的。可以看到一个可执行程序在存储(没有调入内存)时分为代码段、数据区和未初始化数据区三部分。

BSS(未初始化数据区):通常用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS段属于静态分配,程序结束后静态变量资源由系统自动释放。

数据段:存放程序中已初始化的全局变量的一块内存区域。数据段也属于静态内存分配。

代码段:存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域属于只读。在代码段中,也有可能包含一些只读的常数变量

text段和data段在编译时已经分配了空间,而BSS段并不占用可执行文件的大小,它是由链接器来获取内存的。

bss段(未进行初始化的数据)的内容并不存放在磁盘上的程序文件中。其原因是内核在程序开始运行前将它们设置为0。需要存放在程序文件中的只有正文段和初始化数据段。

data段(已经初始化的数据)则为数据分配空间,数据保存到目标文件中。

数据段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到,然后链接器得到这个大小的内存块,紧跟在数据段的后面。当这个内存进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。

可执行程序在运行时又多出两个区域:栈区和堆区。

**栈区:**由编译器自动释放,存放函数的参数值、局部变量等。每当一个函数被调用时,该函数的返回类型和一些调用的信息被存放到栈中。然后这个被调用的函数再为他的自动变量和临时变量在栈上分配空间。每调用一个函数一个新的栈就会被使用。栈区是从高地址位向低地址位增长的,是一块连续的内存区域,最大容量是由系统预先定义好的,申请的栈空间超过这个界限时会提示溢出,用户能从栈中获取的空间较小。

**堆区:**用于动态分配内存,位于BSS和栈中间的地址区域。由程序员申请分配和释放。堆是从低地址位向高地址位增长,采用链式存储结构。频繁的malloc/free造成内存空间的不连续,产生碎片。当申请堆空间时库函数是按照一定的算法搜索可用的足够大的空间。因此堆的效率比栈要低的多。

2.1.6 extern”C” 的作用

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;

而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

2.1.7头文件声明时加extern定义时不要加 因为extern可以多次声明,但只有一个定义

2.1.8函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解

__stdcall和__cdecl都是函数调用约定关键字:

__stdcall:参数由右向左压入堆栈;堆栈由函数本身清理。

__cdecl:参数也是由右向左压入堆栈;但堆栈由调用者清理。

2.1.9重写memcpy()函数需要注意哪些问题

2.1.10数组到底存放在哪里

2.1.11 struct和class的区别

相同点:

struct能包含成员函数吗? 能!

struct能继承吗? 能!!

struct能实现多态吗? 能!!!

不同点:

(1)关于使用大括号初始化

class和struct如果定义了构造函数,就不能用大括号进行初始化了;若没有定义,struct可以用大括号初始化,而class只有在所有成员变量全是public的情况下,才可以用大括号进行初始化。

#include<iostream>

using namespace std;

struct SA

 {

     int a;

      int b;

 };

 SA data1={2,3}; //程序正确

 struct SB

 {

      int a;

      int b;

      SB(int x,int y): a(x),b(y)

      {}

      ~SB(){}

 };

 SB data2={2,3};  //程序错误

 class CA

 {

 public:

      int a;

      int b;

 };

 CA data3={2,3};//正确

 class CB

 {

 public:

      int a;

      int b;

      CB(int x,int y)

           :a(x),b(y)

      {}

      ~CB(){}

 };

 CB data4={2,3};      //错误

 class CC

 {

 public:

      CC(int x,int y)

           :a(x),b(y)

      {}

      ~CC(){}

 private:

      int a;

      int b;

 };

CC data5={2,3};//错误

(2)关于默认权限访问

class中默认成员访问权限是private,而struct的默认访问权限是public

(3)关于继承方式

class中默认继承方式是private,而struct的默认继承方式是public,具体代码如下

2.1.12 char和int之间的转换

将字符char类型转换成int整型

 将字符char类型转换成int整型的方法如下:

    char str_data='5';

    int int_data=(int)(str_data-'0');

    cout<<int_data<<endl;

    同样的道理将int转换成字符char是利用相反的方法;

    int data_int=9;

    char data_str=(char)(data_int+'0');

    cout<<data_str<<endl;

  需要注意的是因为字符只能是单个的字符,所以这种方法处理的int只能是个位数即0-9;

将字符串转化成int

将字符串转化成int的函数是atoi(atoi是C语言库函数)

   char p[]="123456";

    long a=atoi(p);

    cout<<a<<endl;

将int整型转化成字符串型

string str = "123456";

int  a= to_string(str);

2.1.13 static的用法(定义和用途

全局静态变量

在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.

静态存储区,在整个程序运行期间一直存在。

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

局部静态变量

在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。

内存中的位置:静态存储区

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;

静态函数

在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突;

warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰;

类的静态成员

在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用

类的静态函数

静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。

在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);

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

编译器处理不同

宏定义是一个“编译时”概念,在预处理阶段展开(在编译时把所有用到宏定义值的地方用宏定义常量替换),不能对宏定义进行调试,生命周期结束于编译时期;

const常量是一个“运行时”概念,在程序运行使用,类似于一个只读行数据

存储方式不同

宏定义是直接替换,不会分配内存,存储与程序的代码段中;

const常量需要进行内存分配

类型和安全检查不同

宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误;

const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查

2.1.16 volatile作用和用法

作用:

Volatile意思是“易变的”,应该解释为“直接存取原始内存地址”比较合适。 “易变”是因为外在因素引起的,像多线程,中断等;

C语言书籍这样定义volatile关键字:volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

下面是volatile变量的几个例子:

1). 并行设备的硬件寄存器(如:状态寄存器)

2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3). 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。

几个问题:

  1)一个参数既可以是const还可以是volatile吗?

  可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

  2) 一个指针可以是volatile 吗?

  可以,当一个中服务子程序修改一个指向buffer的指针时。

  3). 下面的函数有什么错误:

int square(volatile int *ptr)

{

return *ptr * *ptr;

}

这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b;

a = *ptr;

b = *ptr;

return a * b;

}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

2.1.17 有常量指针 指针常量 常量引用 没有 引用常量

1.指针:指针代表一个变量的地址;

例如:

int a =2,b = 1,*pi = &a;

pi= &b;

2.引用:引用即针对一个变量的别名,引用必须被初始化,引用作为参数(形参)时,不会像指针一样使用存储单元,更不会像值传递一样创建该参数的副本,提高空间/时间效率。

例如:int a=2,&b = a;

3.常量引用:格式为 const 变量类型 &变量名,当声明该引用时,不可通过引用对其目标变量的值进行修改,变量自身可以修改,可用于保证函数内形参不可更改,也就是保证传入的实参为常量。

4.指向常量的指针:(《C++ Primer》书中名字是指向常量的指针,网上的叫法是“常量指针”),const int *p;其本质为一个指针,因为该指针指向一个常量,所以不能通过该指针修改常量的值,但该指针指向的也可为变量,重点在于不能通过该指针修改指向变量(常量)的值;

5.常量指针:(《C++ Primer》书中名字是常量指针,网上的叫法是“指针常量”),int* const p;其本质为一个常量,所以其指向的值可以改变,但是由于指针为常量,所以声明时必须初始化,且初始化后存放在指针中那个地址不可改变,此地址对应的非常量值仍可被改变。

总结: const在 * 的左边,则为指向常量的指针,即指针指向的变量的值不可直接通过指针改变(可以通过其他途径改变);const在 * 的右边,则为常量指针,即指针的指向不可变。简记为const的 “左定值,右定向”。

2.1.19 c/c++中变量的作用域

对局部变量的两点说明:

1)main() 也是一个函数,在 main() 内部定义的变量也是局部变量,只能在 main() 函数内部使用。

2)形参也是局部变量,将实参传递给形参的过程,就是用实参给局部变量赋值的过程,它和a=b; sum=m+n;这样的赋值没有什么区别。

全局变量:

全局变量的默认作用域是整个程序,也就是所有的代码文件,包括源文件(.c文件)和头文件(.h文件)。如果给全局变量加上 static 关键字,它的作用域就变成了当前文件,在其它文件中就无效了。我们目前编写的代码都是在一个源文件中,所以暂时不用考虑 static 关键字

2.1.20 c++中类型转换机制?各适用什么环境?dynamic_cast转换失败时,会出现什么情况?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值