嵌入式linux開發面試題解析——C語言部分
1、編寫統計一個數二進位制表示中有多少個1的函式
int count_bit1(int m)
{
int count = 0;
while(m)
{
m = m & (m-1);
count++;
}
return count;
}
2、編寫一個函式判斷一個數是否是2的N次方
int is_number(int num)
{
if( m & (m - 1) == 0)
return 0;//如果一個數是
else
return 1;//如果一個數不是2的N次方,返回1
}
3、用預處理指令#define 宣告一個常數,用以表明1年中有多少秒(忽略閏年問題)
#define SECONDS_PER_YEAR (1UL*60 * 60 * 24 * 365) 相容16位CPU, #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL這種用法在GCC是不能編譯通過的。
4、前處理器標識#error的目的是什麼?
#error error-message
5、嵌入式系統中經常要用到無限迴圈,你怎麼樣用C編寫死迴圈呢?
while(1) {
; }
6、用變數a給出下面的定義
a) 一個整型數(An integer) b)一個指向整型數的指標( A pointer to an integer) c)一個指向指標的指標,它指向的指標是指向一個整型數( A pointer to a pointer to an integer) d)一個有10個整型數的陣列( An array of 10 integers
e) 一個有10個指標的陣列,該指標是指向一個整型數的。(An array of 10 pointers to integers) f) 一個指向有10個整型數陣列的指標( A pointer to an array of 10 integers) g) 一個指向函式的指標,該函式有一個整型引數並返回一個整型數(A pointer to a function that takes an integer as anargument and returns an integer) h)一個有10個指標的陣列,該指標指向一個函式,該函式有一個整型引數並返回一個整型數( An array of ten pointers to functions that take aninteger argument and return an integer )
定義如下:
a) int a; // An integer b) int *a; // A pointer to an integer c) int **a; // A pointer to a pointer to an integer d) int a[10]; // An array of 10 integers
e) int *a[10]; // Anarray of 10 pointers to integers 等價於int *(a[10]);
f) int (*a)[10]; // Apointer to an array of 10 integers g) int (*max_function)(int a); // A pointer to afunction a that takes an integer argument and returns an integer
h) int (*a[10])(int);// An array of 10 pointers to functions that take an integer argument andreturn an integer
7、鍵字static的作用是什麼?
在C語言中,關鍵字static有三個明顯的作用: A、一旦宣告為靜態變數,在編譯時刻開始永遠存在,不受作用域範圍約束,但是如果是區域性靜態變數,則此靜態變數只能在區域性作用域內使用,超出範圍不能使用,但是它確實還佔用記憶體,還存在. B、在模組內(但在函式體外),一個被宣告為靜態的變數可以被模組內所用函式訪問,但不能被模組外其它函式訪問。它是一個本地的全域性變數。 C、在模組內,一個被宣告為靜態的函式只可被這一模組內的其它函式呼叫。那就是,這個函式被限制在宣告它的模組的本地範圍內使用。
8、typedef與define的區別
typedef是C語言中用來宣告自定義資料型別,配合各種原有資料型別來達到簡化程式設計的目的的型別定義關鍵字。
#define是預處理指令,是巨集定義。
int a = 80;
typedef int * PINT;
const PINT p = &a;//const修飾指標型別,限定指標變數p為只讀
PINT const p = &a;//const修飾指標型別,限定指標變數p為只讀
9、k輸出的值是多少
int main(int argc, char *argv[])
{
char k = 0;
int i;
for(i = 0; i < 127; i++)
k += i & 3;
printf("k = %d\n", k);
return 0;
}
i:0 1 2 3 4 5 6 7
i & 3:0 1 2 3 0 1 2 3
k:0 1 2 3 6 6 7 9
i共計有32組0 1 2 3和一組0 1組成,因此k=32 *(0 + 1 + 2 + 3)+(0 + 1)= 187,由於k為char,所以k在127以後的數是-127,-126....,所以K = (187-127)+ (-127)= -67;
輸出-67;
10、嵌入式系統經常具有要求程式設計師去訪問某特定的記憶體位置的特點。
在某工程中,要求設定一絕對地址為0x67a9的整型變數的值為0xaa66。編譯器是一個純粹的ANSI編譯器。寫程式碼去完成這一任務。解答出如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
嵌入式開發中常用應用:
#define rPCONA(*(volatile unsigned *)0x1D20000)
#define rPDATA(*(volatile unsigned *)0x1D20004)
11、下列程式碼輸出結果是多少
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
解答:
輸出結果是:0 0 1 1
解析:str1,str2,str3,str4是陣列變數,有各自的記憶體空間;而str5,str6,str7,str8是指標,指向相同的常量區域。
12、簡介##和#的作用
前處理器運算子##是連線符號,由兩個井號組成,其功能是在帶引數的巨集定義中將兩個子串(token)聯接起來,從而形成一個新的子串。但它不可以是第一個或者最後一個子串。所謂的子串(token)就是指編譯器能夠識別的最小語法單元。如果替換文字中的引數與##相鄰,則該引數將被實際引數替換,##與前後的空白將被刪除,並對替換後的結果重新掃描。 形成一個新的標號,如果這樣產生的記號無效,或者結果依賴於##運算順序,則結果沒有定義。
#definepaste(front,back)front##back
巨集呼叫paste(name,_xiaobai)的結果為name_xiaobai
#符是把傳遞過來的引數當成字串進行替代
#definedprint(expr)printf(#expr"=%d\n",expr)
inta=20,b=10;
dprint(a/b);
打印出: a/b=2
13、關鍵字volatile有什麼含意?並給出三個不同的例子。
定義為volatile的變數表明變數可能會被意想不到地改變,編譯器就不會去假設這個變數的值。準確地說,優化器在用到volatile修飾的變數時必須每次都小心地重新讀取變數的值,而不是使用儲存在暫存器裡的備份。
使用volatile變數的例子: A、並行裝置的硬體暫存器(如:狀態暫存器)
B、一箇中斷服務子程式中會訪問到的非自動變數(Non-automatic variables) C、多執行緒應用中被幾個任務共享的變數
深入理解:
(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;}
14、嵌入式系統中斷服務子程式(ISR)
中斷是嵌入式系統中重要的組成部分,導致了很多編譯開發商提供一種擴充套件—讓標準C支援中斷。具體代表是,產生了一個新的關鍵字 __interrupt。下面的程式碼就使用了__interrupt關鍵字去定義了一箇中斷服務子程式(ISR),請評論一下這段程式碼的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("Area = %f", area);
return area;
}
中斷程式的特點:
A、ISR 不能返回一個值。
B、ISR 不能傳遞引數。
C、在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的暫存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。
D、printf()經常有重入和效能上的問題。
15、嵌入式C語言開發中的位操作
A、將暫存器指定位(第n位)置為1
GPXX |= (1<<n)
GPXX |= (1<< 7) | (1<< 4 ) | (1<< 0);//第0、4、7位置1,其他保留
B、將暫存器指定位(第n位)置為0
GPXX &= ~(1<<n )
將暫存器的第n位清0,而又不影響其它位的現有狀態。
GPXX &= ~(1<<4 )
C、嵌入式開發位操作例項
unsigned int i = 0x00ff1234;
//i |= (0x1<<13);//bit13置1
//i |= (0xF<<4);//bit4-bit7置1
//i &= ~(1<<17);//清除bit17
//i &= ~(0x1f<<12);//清除bit12開始的5位
//取出bit3-bit8
//i &= (0x3F<<3);//保留bit3-bit8,其他位清零
//i >>= 3;//右移3位
//給暫存器的bit7-bit17賦值937
//i &= ~(0x7FF<<7);//bit7-bit17清零
//i |= (937<<7);//bit7-bit17賦值
//將暫存器bit7-bit17的值加17
// unsigned int a = i;//將a作為i的副本,避免i的其他位被修改
// a &= (0x7FF<<7);//取出bit7-bit17
//a >>= 7;//
// a += 17;//加17
// i &= ~(0x7FF<<7);//將i的bit7-bit17清零
// i |= (a<<7);//將+17後的數寫入bit7-bit17,其他位不變
//給一個暫存器的bit7-bit17賦值937,同時給bit21-bit25賦值17
i &= ~((0x7FF<<7) | (0x1F<<21));//bit7-bit17、bit21-bit25清零
i |= ((937<<7) | (17<<21));//bit7-bit17、bit21-bit25賦值
D、位操作的巨集定義
//用巨集定義將32位數x的第n位(bit0為第1位)置位
#define SET_BIT_N(x,n) (x | (1U<<(n-1)))
//用巨集定義將32位數x的第n位(bit0為第1位)清零
#define CLEAR_BIT_N(x,n) (x & (~(1U<<(n-1))))
//用巨集定義將32位數x的第n位到第m位(bit0為第1位)置位
#define SET_BITS_N_M(x,n,m) (x | (((~0U)>>(32-(m-n+1)))<<(n-1)))
//用巨集定義將32位數x的第n位到第m位(bit0為第1位)清零
#define CLEAR_BITS_N_M(x,n,m) (x & (~(((~0U)>>(32-(m-n+1)))<<(n-1))))
//用巨集定義獲取32位數x的第n位到第m位(bit0為第1位)的部分
#define GET_BITS_N_M(x,n,m) ((x & ~(~(0U)<<(m-n+1))<<(n-1))>>(n-1))
16、大小端的介紹
Little-Endian就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。Big-Endian就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。 數字0x12 34 56 78在記憶體中的表示形式為:
大端模式:
低地址 -----------------> 高地址0x12 | 0x34 | 0x56 | 0x78
小端模式:
低地址 ------------------> 高地址0x78 | 0x56 | 0x34 | 0x12
Big-Endian: 低地址存放高位,如下:高地址 --------------- buf[3] (0x78) -- 低位 buf[2] (0x56) buf[1] (0x34) buf[0] (0x12) -- 高位 --------------- 低地址Little-Endian: 低地址存放低位,如下:高地址 --------------- buf[3] (0x12) -- 高位 buf[2] (0x34) buf[1] (0x56) buf[0] (0x78) -- 低位 --------------低地址
C語言實現測試大小端:
#include <stdio.h>
int main(int argc, char **argv)
{
union
{
int a;
char b;
}c;
c.a = 1;
if(c.b == 1)
{
printf("little\n");
}
else
printf("big\n");
return 0;
}
常見CPU的大小端:
Big Endian : PowerPC、IBM、Sun Little Endian : x86、DEC
常見檔案的位元組序:
Adobe PS – Big Endian BMP – Little Endian DXF(AutoCAD) – Variable GIF – Little Endian JPEG – Big Endian MacPaint – Big Endian RTF – Little Endian
Java和所有的網路通訊協議都是使用Big-Endian的編碼。
大小端的轉換:
16位
#define BigtoLittle16(A) (( ((uint16)(A) & 0xff00) >> 8) | \
(( (uint16)(A) & 0x00ff) << 8))
32位
#define BigtoLittle32(A) ((( (uint32)(A) & 0xff000000) >> 24) | \
(( (uint32)(A) & 0x00ff0000) >> 8) | \
(( (uint32)(A) & 0x0000ff00) << 8) | \
(( (uint32)(A) & 0x000000ff) << 24))
從軟體的角度上,不同端模式的處理器進行資料傳遞時必須要考慮端模式的不同。如進行網路資料傳遞時,必須要考慮端模式的轉換。在Socket介面程式設計中,以下幾個函式用於大小端位元組序的轉換。
ntohs(n) //16位資料型別網路位元組順序到主機位元組順序的轉換
htons(n) //16位資料型別主機位元組順序到網路位元組順序的轉換
ntohl(n) //32位資料型別網路位元組順序到主機位元組順序的轉換
htonl(n) //32位資料型別主機位元組順序到網路位元組順序的轉換
17、引用和指標有什麼區別?
A、應用必須初始化,指標不必;
B、引用處畫化後不能改變,指標可以被改變;
C、不存在指向空值的引用,但存在指向空值的指標;
18、寫出float,bool,int型別與零的比較,假設變數為X:
Int : if(x==0)
Float: if(x> -0.0000001&&x<0.0000001)
Bool: if(x==false)
19、多維陣列的定義
在陣列定義int a[2][2]={{3},{2,3}};則a[0][1]的值為0。(對)
#include <stdio.h>
intmain(int argc,char * argv[])
{
int a [3][2]={(0,1),(2,3),(4,5)};
int *p;
p=a [0];
printf("%d",p[0]);
}
問打印出來結果是多少?
答案:1
分析:花括號裡巢狀的是小括號而不是花括號!這裡是花括號裡面嵌套了逗號表示式!其實這個賦值就相當於int a [3][2]={ 1, 3, 5};
20、評價下面的程式碼片斷
unsigned int zero = 0; unsigned int compzero = 0xFFFF;
對於一個int型不是16位的處理器為說,上面的程式碼是不正確的。應編寫如下:
unsigned int compzero = ~0;
21、.h標頭檔案中的ifndef/define/endif 的作用? 答:防止該標頭檔案被重複引用。
22、#include 與 #include “file.h”的區別? 答:前者是從Standard Library的路徑尋找和引用file.h,而後者是從當前工作路徑搜尋並引用file.h。
23、描述實時系統的基本特性 答 :在特定時間內完成特定的任務,實時性與可靠性。
24、全域性變數和區域性變數在記憶體中是否有區別?如果有,是什麼區別? 答 :全域性變數儲存在靜態資料區,區域性變數在堆疊中。
25、堆疊溢位一般是由什麼原因導致的? 答 : A、沒有回收垃圾資源 B、層次太深的遞迴呼叫
26、不能做switch()的引數型別 答 :switch的引數不能為實型。
27、區域性變數能否和全域性變數重名? 答:能,區域性會遮蔽全域性。要用全域性變數,需要使用”::”區域性變數可以與全域性變數同名,在函式內引用這個變數時,會用到同名的區域性變數,而不會用到全域性變數。對於有些編譯器而言,在同一個函式內可以定義多個同名的區域性變數,比如在兩個迴圈體內都定義一個同名的區域性變數,而那個區域性變數的作用域就在那個迴圈體內。
28、如何引用一個已經定義過的全域性變數? 答 :可以用引用標頭檔案的方式,也可以用extern關鍵字,如果用引用標頭檔案方式來引用某個在標頭檔案中宣告的全域性變數,假定你將那個變數寫錯了,那麼在編譯期間會報錯,如果你用extern方式引用時,假定你犯了同樣的錯誤,那麼在編譯期間不會報錯,而在連線期間報錯。
29、全域性變數可不可以定義在可被多個.C檔案包含的標頭檔案中?為什麼? 答 :可以,在不同的C檔案中以static形式來宣告同名全域性變數。可以在不同的C檔案中宣告同名的全域性變數,前提是其中只能有一個C檔案中對此變數賦初值,此時連線不會出錯。
30、do……while和while……有什麼區別? 答 :前一個迴圈一遍再判斷,後一個判斷以後再迴圈。
31、程式的記憶體分配 答:一個由C/C++編譯的程式佔用的記憶體分為以下幾個部分 A、棧區(stack)—由編譯器自動分配釋放,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。 B、堆區(heap)—一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由OS回收。注意它與資料結構中的堆是兩回事,分配方式倒是類似於連結串列,呵呵。 C、全域性區(靜態區)(static)—全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。程式結束後由系統釋放。 D、文字常量區—常量字串就是放在這裡的。程式結束後由系統釋放。