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

文字常量區,叫.rodata,不可以改變,改變會導致段錯誤
- 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 :全局靜態未初始化變量;生命周期為整個程序運行期間;作用域為當前文件;儲存位置為BSS段。
a2 :全局靜態變量
a3 :全局初始化變量;其他同a0。
a4 :局部變量;生命周期為fun函數運行期間;作用域為fun函數內部;儲存位置為棧。
a5 :局部易變變量;
14、使用32位編譯情況下,給出判斷所使用機器大小端的方法。

聯合體方法判斷方法:利用union結構體的從低地址開始存,且同一時間內只有一個成員占有內存的特性。大端儲存符合閱讀習慣。聯合體占用內存是最大的那個,和結構體不一樣。
a和c公用同一片內存區域,所以更改c,必然會影響a的數據
- #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強制類型轉換成char單字節,p指向a的起始字節(低字節)
- #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) 一個整型數;
- b)一個指向整型數的指針;
- c)一個指向指針的指針,它指向的指針是指向一個整型數;
- d)一個有10個整型的數組;
- e)一個有10個指針的數組,該指針是指向一個整型數;
- f)一個指向有10個整型數數組的指針;
- g)一個指向函數的指針,該函數有一個整型參數并返回一個整型數;
- h)一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數并返回一個整型數
- 答案:
- 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、與或非,異或。運算符優先級
sum=a&b<
其中a=3,b=5,c=4(先加再移位再&再異或)答案4
