嵌入式筆試面試題目系列(二)
一、前言
本系列將按類別對題目進行分類整理,重要的地方標(biāo)上星星,這樣有利于大家打下堅實的基礎(chǔ)。
本文比較深入,需要花時間去理解,保證都是必考題。文章較長,可以先收藏,找個大塊時間看。一遍看不懂可以多看幾遍。
下面分享16個重要知識點。
二、C/C++題目
1、new和malloc
做嵌入式,對于內(nèi)存是十分在意的,因為可用內(nèi)存有限,所以嵌入式筆試面試題目,內(nèi)存的題目高頻。
1)malloc和free是c++/c語言的庫函數(shù),需要頭文件支持stdlib.h;new和delete是C++的關(guān)鍵字,不需要頭文件,需要編譯器支持;
2)使用new操作符申請內(nèi)存分配時,無需指定內(nèi)存塊的大小,編譯器會根據(jù)類型信息自行計算。而malloc則需要顯式地支持所需內(nèi)存的大小。
3)new操作符內(nèi)存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無需進行類型轉(zhuǎn)換,故new是符合類型安全性的操作符。而malloc內(nèi)存分配成功則是返回void*,需要通過強制類型轉(zhuǎn)換將void*指針轉(zhuǎn)換成我們需要的類型。
4)new內(nèi)存分配失敗時,會拋出bad_alloc異常。malloc分配內(nèi)存失敗時返回NULL。
2、在1G內(nèi)存的計算機中能否malloc(1.2G)?為什么?(2021浙江大華二面問題)
答:是有可能申請1.2G的內(nèi)存的。
解析:回答這個問題前需要知道m(xù)alloc的作用和原理,應(yīng)用程序通過malloc函數(shù)可以向程序的虛擬空間申請一塊虛擬地址空間,與物理內(nèi)存沒有直接關(guān)系,得到的是在虛擬地址空間中的地址,之后程序運行所提供的物理內(nèi)存是由操作系統(tǒng)完成的。
3 、extern”C” 的作用
我們可以在C++中使用C的已編譯好的函數(shù)模塊,這時候就需要用到extern”C”。也就是extern“C” 都是在c++文件里添加的。
extern在鏈接階段起作用(四大階段:預(yù)處理--編譯--匯編--鏈接)。
4、strcat、strncat、strcmp、strcpy哪些函數(shù)會導(dǎo)致內(nèi)存溢出?如何改進?(2021浙江大華二面問題)
strcpy函數(shù)會導(dǎo)致內(nèi)存溢出。
strcpy拷貝函數(shù)不安全,他不做任何的檢查措施,也不判斷拷貝大小,不判斷目的地址內(nèi)存是否夠用。
- char *strcpy(char *strDest,const char *strSrc)
strncpy拷貝函數(shù),雖然計算了復(fù)制的大小,但是也不安全,沒有檢查目標(biāo)的邊界。
- strncpy(dest, src, sizeof(dest));
strncpy_s是安全的
strcmp(str1,str2),是比較函數(shù),若str1=str2,則返回零;若str1
strncat()主要功能是在字符串的結(jié)尾追加n個字符。
- char * strncat(char *dest, const char *src, size_t n);
strcat()函數(shù)主要用來將兩個char類型連接。例如:
- char d[20]="Golden";
- char s[20]="View";
- strcat(d,s);
- //打印d
- printf("%s",d);
輸出 d 為 GoldenView (中間無空格)
延伸:
memcpy拷貝函數(shù),它與strcpy的區(qū)別就是memcpy可以拷貝任意類型的數(shù)據(jù),strcpy只能拷貝字符串類型。
memcpy 函數(shù)用于把資源內(nèi)存(src所指向的內(nèi)存區(qū)域)拷貝到目標(biāo)內(nèi)存(dest所指向的內(nèi)存區(qū)域);有一個size變量控制拷貝的字節(jié)數(shù);
函數(shù)原型:
- void *memcpy(void *dest, void *src, unsigned int count);
5 、static的用法(定義和用途)(必考)
1)用static修飾局部變量:使其變?yōu)殪o態(tài)存儲方式(靜態(tài)數(shù)據(jù)區(qū)),那么這個局部變量在函數(shù)執(zhí)行完成之后不會被釋放,而是繼續(xù)保留在內(nèi)存中。
2)用static修飾全局變量:使其只在本文件內(nèi)部有效,而其他文件不可連接或引用該變量。
3)用static修飾函數(shù):對函數(shù)的連接方式產(chǎn)生影響,使得函數(shù)只在本文件內(nèi)部有效,對其他文件是不可見的(這一點在大工程中很重要很重要,避免很多麻煩,很常見)。這樣的函數(shù)又叫作靜態(tài)函數(shù)。使用靜態(tài)函數(shù)的好處是,不用擔(dān)心與其他文件的同名函數(shù)產(chǎn)生干擾,另外也是對函數(shù)本身的一種保護機制。
6、const的用法(定義和用途)(必考)
const主要用來修飾變量、函數(shù)形參和類成員函數(shù):
1)用const修飾常量:定義時就初始化,以后不能更改。
2)用const修飾形參:func(const int a){};該形參在函數(shù)里不能改變
3)用const修飾類成員函數(shù):該函數(shù)對成員變量只能進行只讀操作,就是const類成員函數(shù)是不能修改成員變量的數(shù)值的。
被const修飾的東西都受到強制保護,可以預(yù)防意外的變動,能提高程序的健壯性。
參考一個大佬的回答:
我只要一聽到被面試者說:"const意味著常數(shù)",我就知道我正在和一個業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應(yīng)該非常熟悉const能做什么和不能做什么.如果你從沒有讀到那篇文章,只要能說出const意味著"只讀"就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正確的答案。如果應(yīng)試者能正確回答這個問題,我將問他一個附加的問題:下面的聲明都是什么意思?
- const int a;
- int const a;
- const int *a;
- int * const a;
- int const * a const;
前兩個的作用是一樣,a是一個常整型數(shù)。
第三個意味著a是一個指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。
第四個意思a是一個指向整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。
最后一個意味著a是一個指向常整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是不可修改的,同時指針也是不可修改的)。
7、volatile作用和用法
一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設(shè)這個變量的值了。精確地說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量在內(nèi)存中的值,而不是使用保存在寄存器里的備份(雖然讀寫寄存器比讀寫內(nèi)存快)。
回答不出這個問題的人是不會被雇傭的。這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問題。搞嵌入式的家伙們經(jīng)常同硬件、中斷、RTOS等等打交道,所有這些都要求用到volatile變量。不懂得volatile的內(nèi)容將會帶來災(zāi)難。
以下幾種情況都會用到volatile:
1、并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)2、一個中斷服務(wù)子程序中會訪問到的非自動變量3、多線程應(yīng)用中被幾個任務(wù)共享的變量
8、const常量和#define的區(qū)別(編譯階段、安全性、內(nèi)存占用等)
用#define max 100 ; 定義的常量是沒有類型的(不進行類型安全檢查,可能會產(chǎn)生意想不到的錯誤),所給出的是一個立即數(shù),編譯器只是把所定義的常量值與所定義的常量的名字聯(lián)系起來,define所定義的宏變量在預(yù)處理階段的時候進行替換,在程序中使用到該常量的地方都要進行拷貝替換;
用const int max = 255 ; 定義的常量有類型(編譯時會進行類型檢查)名字,存放在內(nèi)存的靜態(tài)區(qū)域中,在編譯時確定其值。在程序運行過程中const變量只有一個拷貝,而#define所定義的宏變量卻有多個拷貝,所以宏定義在程序運行過程中所消耗的內(nèi)存要比const變量的大得多
9、變量的作用域(全局變量和局部變量)
全局變量:在所有函數(shù)體的外部定義的,程序的所在部分(甚至其它文件中的代碼)都可以使用。全局變量不受作用域的影響(也就是說,全局變量的生命期一直到程序的結(jié)束)。
局部變量:出現(xiàn)在一個作用域內(nèi),它們是局限于一個函數(shù)的。局部變量經(jīng)常被稱為自動變量,因為它們在進入作用域時自動生成,離開作用域時自動消失。關(guān)鍵字auto可以顯式地說明這個問題,但是局部變量默認為auto,所以沒有必要聲明為auto。
局部變量可以和全局變量重名,在局部變量作用域范圍內(nèi),全局變量失效,采用的是局部變量的值。
10、sizeof 與strlen (字符串,數(shù)組)
1.如果是數(shù)組
- #include<stdio.h>
- int main()
- {
- int a[5]={1,2,3,4,5};
- printf(“sizeof 數(shù)組名=%d\n”,sizeof(a));
- printf(“sizeof *數(shù)組名=%d\n”,sizeof(*a));
- }
運行結(jié)果
- sizeof 數(shù)組名=20
- sizeof *數(shù)組名=4
2.如果是指針,sizeof只會檢測到是指針的類型,指針都是占用4個字節(jié)的空間(32位機)。
sizeof是什么?是一個操作符,也是關(guān)鍵字,就不是一個函數(shù),這和strlen()不同,strlen()是一個函數(shù)。
那么sizeof的作用是什么?返回一個對象或者類型所占的內(nèi)存字節(jié)數(shù)。我們會對sizeof()中的數(shù)據(jù)或者指針做運算嗎?基本不會。例如sizeof(1+2.0),直接檢測到其中類型是double,即是sizeof(double) = 8。如果是指針,sizeof只會檢測到是指針的類型,指針都是占用4個字節(jié)的空間(32位機)。
- char *p = "sadasdasd";
- sizeof(p):4
- sizeof(*p):1//指向一個char類型的
除非使用strlen(),僅對字符串有效,直到'\0'為止了,計數(shù)結(jié)果不包括\0。
要是非要使用sizeof來得到指向內(nèi)容的大小,就得使用數(shù)組名才行, 如
- char a[10];
- sizeof(a):10 //檢測到a是一個數(shù)組的類型。
關(guān)于strlen(),它是一個函數(shù),考察的比較簡單:
- strlen “\n\t\tag\AAtang”
答案:11
11、經(jīng)典的sizeof(struct)和sizeof(union)內(nèi)存對齊
內(nèi)存對齊作用:
1.平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
2.性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問。
結(jié)構(gòu)體struct內(nèi)存對齊的3大規(guī)則:
1.對于結(jié)構(gòu)體的各個成員,第一個成員的偏移量是0,排列在后面的成員其當(dāng)前偏移量必須是當(dāng)前成員類型的整數(shù)倍;
2.結(jié)構(gòu)體內(nèi)所有數(shù)據(jù)成員各自內(nèi)存對齊后,結(jié)構(gòu)體本身還要進行一次內(nèi)存對齊,保證整個結(jié)構(gòu)體占用內(nèi)存大小是結(jié)構(gòu)體內(nèi)最大數(shù)據(jù)成員的最小整數(shù)倍;
3.如程序中有#pragma pack(n)預(yù)編譯指令,則所有成員對齊以n字節(jié)為準(zhǔn)(即偏移量是n的整數(shù)倍),不再考慮當(dāng)前類型以及最大結(jié)構(gòu)體內(nèi)類型。
- #pragma pack(1)
- struct fun{
- int i;
- double d;
- char c;
- };
sizeof(fun) = 13
- struct CAT_s
- {
- int ld;
- char Color;
- unsigned short Age;
- char *Name;
- void(*Jump)(void);
- }Garfield;
1.使用32位編譯,int占4, char 占1, unsigned short 占2,char* 占4,函數(shù)指針占4個,由于是32位編譯是4字節(jié)對齊,所以該結(jié)構(gòu)體占16個字節(jié)。(說明:按幾字節(jié)對齊,是根據(jù)結(jié)構(gòu)體的最長類型決定的,這里是int是最長的字節(jié),所以按4字節(jié)對齊);
2.使用64位編譯 ,int占4, char 占1, unsigned short 占2,char* 占8,函數(shù)指針占8個,由于是64位編譯是8字節(jié)對齊(說明:按幾字節(jié)對齊,是根據(jù)結(jié)構(gòu)體的最長類型決定的,這里是函數(shù)指針是最長的字節(jié),所以按8字節(jié)對齊)所以該結(jié)構(gòu)體占24個字節(jié)。
- //64位
- struct C
- {
- double t; //8 1111 1111
- char b; //1 1
- int a; //4 0001111
- short c; //2 11000000
- };
- sizeof(C) = 24; //注意:1 4 2 不能拼在一起
char是1,然后在int之前,地址偏移量得是4的倍數(shù),所以char后面補三個字節(jié),也就是char占了4個字節(jié),然后int四個字節(jié),最后是short,只占兩個字節(jié),但是總的偏移量得是double的倍數(shù),也就是8的倍數(shù),所以short后面補六個字節(jié)
聯(lián)合體union內(nèi)存對齊的2大規(guī)則:
1.找到占用字節(jié)最多的成員;
2.union的字節(jié)數(shù)必須是占用字節(jié)最多的成員的字節(jié)的倍數(shù),而且需要能夠容納其他的成員
- //x64
- typedef union {
- long i;
- int k[5];
- char c;
- }D
要計算union的大小,首先要找到占用字節(jié)最多的成員,本例中是long,占用8個字節(jié),int k[5]中都是int類型,仍然是占用4個字節(jié)的,然后union的字節(jié)數(shù)必須是占用字節(jié)最多的成員的字節(jié)的倍數(shù),而且需要能夠容納其他的成員,為了要容納k(20個字節(jié)),就必須要保證是8的倍數(shù)的同時還要大于20個字節(jié),所以是24個字節(jié)。
引申:位域(大疆筆試題)
C語言允許在一個結(jié)構(gòu)體中以位為單位來指定其成員所占內(nèi)存長度,這種以位為單位的成員稱為“位段”或稱“位域”( bit field) 。利用位段能夠用較少的位數(shù)存儲數(shù)據(jù)。一個位段必須存儲在同一存儲單元中,不能跨兩個單元。如果第一個單元空間不能容納下一個位段,則該空間不用,而從下一個單元起存放該位段。
1.位段聲明和結(jié)構(gòu)體類似
2.位段的成員必須是int、unsigned int、signed int
3.位段的成員名后邊有一個冒號和一個數(shù)字
- typedef struct_data{
- char m:3;
- char n:5;
- short s;
- union{
- int a;
- char b;
- };
- int h;
- }_attribute_((packed)) data_t;
答案12
m和n一起,剛好占用一個字節(jié)內(nèi)存,因為后面是short類型變量,所以在short s之前,應(yīng)該補一個字節(jié)。所以m和n其實是占了兩個字節(jié)的,然后是short兩個個字節(jié),加起來就4個字節(jié),然后聯(lián)合體占了四個字節(jié),總共8個字節(jié)了,最后int h占了四個字節(jié),就是12個字節(jié)了
attribute((packed)) 取消對齊
GNU C的一大特色就是__attribute__機制。__attribute__可以設(shè)置函數(shù)屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute)。
__attribute__書寫特征是:__attribute__前后都有兩個下劃線,并且后面會緊跟一對括弧,括弧里面是相應(yīng)的__attribute__參數(shù)。
跨平臺通信時用到。不同平臺內(nèi)存對齊方式不同。如果使用結(jié)構(gòu)體進行平臺間的通信,會有問題。例如,發(fā)送消息的平臺上,結(jié)構(gòu)體為24字節(jié),接受消息的平臺上,此結(jié)構(gòu)體為32字節(jié)(只是隨便舉個例子),那么每個變量對應(yīng)的值就不對了。
不同框架的處理器對齊方式會有不同,這個時候不指定對齊的話,會產(chǎn)生錯誤結(jié)果
12、inline函數(shù)
在C語言中,如果一些函數(shù)被頻繁調(diào)用,不斷地有函數(shù)入棧,即函數(shù)棧,會造成??臻g或棧內(nèi)存的大量消耗。為了解決這個問題,特別的引入了inline修飾符,表示為內(nèi)聯(lián)函數(shù)。
大多數(shù)的機器上,調(diào)用函數(shù)都要做很多工作:調(diào)用前要先保存寄存器,并在返回時恢復(fù),復(fù)制實參,程序還必須轉(zhuǎn)向一個新位置執(zhí)行C++中支持內(nèi)聯(lián)函數(shù),其目的是為了提高函數(shù)的執(zhí)行效率,用關(guān)鍵字 inline 放在函數(shù)定義(注意是定義而非聲明)的前面即可將函數(shù)指定為內(nèi)聯(lián)函數(shù),內(nèi)聯(lián)函數(shù)通常就是將它在程序中的每個調(diào)用點上“內(nèi)聯(lián)地”展開。
內(nèi)聯(lián)是以代碼膨脹(復(fù)制)為代價,僅僅省去了函數(shù)調(diào)用的開銷,從而提高函數(shù)的執(zhí)行效率。
13、內(nèi)存四區(qū),什么變量分別存儲在什么區(qū)域,堆上還是棧上。

文字常量區(qū),叫.rodata,不可以改變,改變會導(dǎo)致段錯誤
- int a0=1;
- static int a1;
- const static a2=0;
- extern int a3;
- void fun(void)
- {
- int a4;
- volatile int a5;
- return;
- }
a0 :全局初始化變量;生命周期為整個程序運行期間;作用域為所有文件;存儲位置為data段。
a1 :全局靜態(tài)未初始化變量;生命周期為整個程序運行期間;作用域為當(dāng)前文件;儲存位置為BSS段。
a2 :全局靜態(tài)變量
a3 :全局初始化變量;其他同a0。
a4 :局部變量;生命周期為fun函數(shù)運行期間;作用域為fun函數(shù)內(nèi)部;儲存位置為棧。
a5 :局部易變變量;
14、使用32位編譯情況下,給出判斷所使用機器大小端的方法。

聯(lián)合體方法判斷方法:利用union結(jié)構(gòu)體的從低地址開始存,且同一時間內(nèi)只有一個成員占有內(nèi)存的特性。大端儲存符合閱讀習(xí)慣。聯(lián)合體占用內(nèi)存是最大的那個,和結(jié)構(gòu)體不一樣。
a和c公用同一片內(nèi)存區(qū)域,所以更改c,必然會影響a的數(shù)據(jù)
- #include<stdio.h>
- int main(){
- union w
- {
- int a;
- char b;
- }c;
- c.a = 1;
- if(c.b == 1)
- printf("小端存儲\n");
- else
- printf("大端存儲\n");
- return 0;
- }
指針方法
通過將int強制類型轉(zhuǎn)換成char單字節(jié),p指向a的起始字節(jié)(低字節(jié))
- #include <stdio.h>
- int main ()
- {
- int a = 1;
- char *p = (char *)&a;
- if(*p == 1)
- {
- printf("小端存儲\n");
- }
- else
- {
- printf("大端存儲\n");
- }
- return 0;
- }
15、用變量a給出下面的定義
- a) 一個整型數(shù);
- b)一個指向整型數(shù)的指針;
- c)一個指向指針的指針,它指向的指針是指向一個整型數(shù);
- d)一個有10個整型的數(shù)組;
- e)一個有10個指針的數(shù)組,該指針是指向一個整型數(shù);
- f)一個指向有10個整型數(shù)數(shù)組的指針;
- g)一個指向函數(shù)的指針,該函數(shù)有一個整型參數(shù)并返回一個整型數(shù);
- h)一個有10個指針的數(shù)組,該指針指向一個函數(shù),該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)
- 答案:
- a)int a
- b)int *a;
- c)int **a;
- d)int a[10];
- e)int *a [10];
- f) int a[10], *p=a;
- g)int (*a)(int)
- h) int( *a[10])(int)
16、與或非,異或。運算符優(yōu)先級
sum=a&b<
其中a=3,b=5,c=4(先加再移位再&再異或)答案4
