C語言面試題
關於“一年有多少秒”的巨集定義
網上關於這個問題的答案都是(365*24*60*60)UL,是錯誤的。
正解:數字和型別是一個整體,()是外人。
#include <stdio.h>
#define SECONDS_PER_YEAR (365*24*60*60UL)
int main()
{
printf("one year has %ld sec\r\n",SECONDS_PER_YEAR);
return 0;
}
寫一個標準巨集MIN,這個巨集輸入兩個引數並返回較小的那個。另外當寫下least=MIN(*p++,b)時會發生什麼
- 謹慎地將巨集定義中的“引數”和整個巨集虹括號括起來
- 防止巨集的副作用。巨集定義#define MIN(A,B) ((A) <= (B) ? (A) : (B)) 對MIN(*p++,
b)的作用結果是 ((*p++) <= (b) ? (*p++) : (*p++))這個表示式會產生副作用,指標p會作三次++自增操作 - 除此之外,另一個不好的解答是#define MIN(A,B) ((A) <= (B) ? (A) : (B));
這個解答在巨集定義的後面加了“;“
#include <stdio.h>
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
int a = 100 ;
int b = 200 ;
int* p = &a ;
int main()
{
printf("p is %d\r\n",(int)p);
printf("the min is %d\r\n",MIN(*p++,b));
printf("p is %d\r\n",(int)p);
printf("a is %d\r\n",a);
return 0;
}
用C編寫死迴圈
while(1)
{
}
do
{
}while(1);
for(;;)
{
}
Loop:
...
goto Loop ;
指標陣列與陣列指標
- 指標陣列:指標陣列可以說成是”指標的陣列”,首先這個變數是一個數組,其次,”指標”修飾這個陣列,意思是說這個陣列的所有元素都是指標型別,在32位系統中,指標佔四個位元組。
- 陣列指標:陣列指標可以說成是”陣列的指標”,首先這個變數是一個指標,其次,”陣列”修飾這個指標,意思是說這個指標存放著一個數組的首地址,或者說這個指標指向一個數組的首地址。
char *arr[4] = {"hello", "world", "shannxi", "xian"};
//arr就是我定義的一個指標陣列,它有四個元素,每個元素是一個char *型別的指標,這些指標存放著其對應字串的首地址。
記憶體映像圖
陣列指標
首先來定義一個數組指標,既然是指標,名字就叫pa
char (*pa)[4];
既然pa是一個指標,存放一個數組的地址,那麼在我們定義一個數組時,陣列名稱就是這個陣列的首地址,那麼這二者有什麼區別和聯絡呢?
- a是一個長度為4的字元陣列,a是這個陣列的首元素首地址。既然a是地址,pa是指向陣列的指標,那麼能將a賦值給pa嗎?答案是不行的!因為a是陣列首元素首地址,pa存放的卻是陣列首地址,a是char型別,a+1,a的值會實實在在的加1,而pa是char[4]型別的,pa+1,pa則會加4,雖然陣列的首地址和首元素首地址的值相同,但是兩者操作不同,所以型別不匹配不能直接賦值,但是可以這樣:pa = &a,pa相當與二維陣列的行指標,現在它指向a[4]的地址。
C語言中static關鍵字的作用
- 第一、在修飾變數的時候,static修飾的靜態區域性變數只執行一次,而且延長了區域性變數的生命週期,直到程式執行結束以後才釋放。
- 第二、static修飾全域性變數的時候,這個全域性變數只能在本檔案中訪問,不能在其它檔案中訪問,即便是extern外部宣告也不可以。
- 第三、static修飾一個函式,則這個函式的只能在本檔案中呼叫,不能被其他檔案呼叫。Static修飾的區域性變數存放在全域性資料區的靜態變數區。初始化的時候自動初始化為0;
const用途
const int *ptr; /*ptr為指向整型常量的指標,ptr的值可以修改,但不能修改其所指向的值*/
int *const ptr;/*ptr為指向整型的常量指標,ptr的值不能修改,但可以修改其所指向的值*/
const int *const ptr;/*ptr為指向整型常量的常量指標,ptr及其指向的值都不能修改*/
用途
- 修飾函式形參,使得形參在函式內不能被修改,表示輸入引數。
int fun(const int a);
int fun(const char *str);
- 修飾函式返回值,使得函式的返回值不能被修改。
const char *getstr(void); //使用:const *str= getstr();
const int getint(void); //使用:const int a =getint();
volatile關鍵字的作用
volatile指定的關鍵字可能被系統、硬體、程序/執行緒改變,強制編譯器每次從記憶體中取得該變數的值,而不是從被優化後的暫存器中讀取。例子:硬體時鐘;多執行緒中被多個任務共享的變數等。
extern關鍵字的作用
用於修飾變數或函式,表明該變數或函式都是在別的檔案中定義的,提示編譯器在其他檔案中尋找定義.
extern “c”
extern “c”的作用就是為了能夠正確實現C++程式碼呼叫其他C語言程式碼。加上extern “C”後,會指示編譯器這部分程式碼按C語言的編譯方式進行編譯,而不是C++的。
#ifdef __cplusplus
extern “C”
{
#endif
extern void fun(int a, int b);
#ifdef __cplusplus
}
#endif
不可以將extern”C” 新增在函式內部
sizeof關鍵字的作用
sizeof是在編譯階段處理,且不能被編譯為機器碼。sizeof的結果等於物件或型別所佔的記憶體位元組數。sizeof的返回值型別為size_t。
- 變數:int a; sizeof(a)為4;
- 指標:int *p; sizeof(p)為4;
- 陣列: int b[10]; sizeof(b)為陣列的大小,4*10; int c[0]; sizeof(c)等於0
- 結構體:struct (int a; char ch;)s1; sizeof(s1)為8 與結構體位元組對齊有關。
- 注意:不能對結構體中的位域成員使用sizeof
- sizeof(void)等於1
- sizeof(void *)等於4
typedef
typedef struct st
{
int a;
int b;
char c[0];
}st_t;
sizeof(st_t)等於8,即char c[0]的大小為0.
ASSERT() 和 assert()
:ASSERT()是一個除錯程式時經常使用的巨集,在程式執行時它計算括號內的表示式,如果表示式為FALSE (0), 程式將報告錯誤,並終止執行。如果表示式不為0,則繼續執行後面的語句。這個巨集通常原來判斷程式中是否出現了明顯非法的資料,如果出現了終止程式以免導致嚴重後果,同時也便於查詢錯誤。
- ASSERT只有在Debug版本中才有效,如果編譯為Release版本則被忽略。
- assert()的功能類似,它是ANSI C標準中規定的函式,它與ASSERT的一個重要區別是可以用在Release版本中。
strlen字串長度改錯
void test1()
{
char string[10];
char* str1 = "0123456789";
strcpy(string, str1);
}
解答:字串str1有11個位元組(包括末尾的結束符’\0’),而string只有10個位元組,故而strcpy會導致陣列string越界。
void test2()
{
char string[10], str1[10];
int i;
for(i=0; i<10; i++)
{
str1= 'a';
}
strcpy(string, str1);
}
解答:因為str1沒有結束符’\0’,故而strcpy複製的字元數不確定。
void test3(char* str1)
{
char string[10];
if(strlen(str1) <= 10 )
{
strcpy(string, str1);
}
}
解答:應修改為if (strlen(str1) < 10),因為strlen的結果未統計最後的結束符’\0’。
void GetMemory(char *p)
{
p = (char *)malloc( 100 );
}
void Test( void )
{
char *str = NULL;
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}
解答:C語言中的函式引數為傳值引數,在函式內對形參的修改並不能改變對應實參的值。故而呼叫GetMemory後,str仍為NULL。
char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf(str);
}
解答:GetMemory中,p為區域性變數,在函式返回後,該區域性變數被回收。故而str仍為NULL
void GetMemory( char **p, int num )
{
*p = (char *)malloc(num);
}
void Test( void )
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
解答:試題6避免了試題4的問題,但在GetMemory內,未對*p為NULL情況的判斷。當*p不為NULL時,在printf後,也未對malloc的空間進行free
void Test( void )
{
char *str = (char *)malloc( 100 );
strcpy(str, "hello" );
free(str);
... //省略的其它語句
}
解答:未對str為NULL的情況的判斷,在free(str)後,str未設定為NULL,可能變成一個野指標(後面對str的操作可能會導致踩記憶體)。
swap(int* p1,int* p2)
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
解答:在swap函式中,p是個野指標,可能指向系統區,導致程式執行的崩潰。故而,程式應改為:
swap(int* p1,int* p2)
{
int p;
p = *p1;
*p1 = *p2;
*p2 = p;
}
程式設計題:判斷字串str2是否在字串str1裡
#include <stdio.h>
#define OK 1
#define ERROR 0
int str_str(const char *str1, const char *str2)
{
const char *s1 = NULL;
const char *s2 = NULL;
if (str1 == NULL)
{
return (str2 == NULL) ? OK : ERROR;
}
if (str2 == NULL)
{
return OK;
}
for (; *str1 != '\0'; str1++)
{
if (*str1 == *str2)
{
for (s1 = str1, s2 = str2; ; )
{
if (*++s2 == '\0')
{
return OK;
}
else if (*++s1 != *s2)
{
break;
}
}
}
}
return ERROR;
}
結構體大小、記憶體對齊方式
首先是各編譯器下sizeof()值:
- 32位編譯器:32位系統下指標佔用4位元組
char :1個位元組
char*(即指標變數): 4個位元組(32位的定址空間是2^32, 即32個bit,也就是4個位元組。同理64位編譯器)
short int : 2個位元組
int: 4個位元組
unsigned int : 4個位元組
float: 4個位元組
double: 8個位元組
long: 4個位元組
long long: 8個位元組
unsigned long: 4個位元組
- 64位編譯器:64位系統下指標佔用8位元組
char :1個位元組
char*(即指標變數): 8個位元組
short int : 2個位元組
int: 4個位元組
unsigned int : 4個位元組
float: 4個位元組
double: 8個位元組
long: 8個位元組
long long: 8個位元組
unsigned long: 8個位元組
結構體內對齊規則:
- 每個成員分別按自己的對齊位元組數和PPB(指定的對齊位元組數,32位機預設為4)兩個位元組數最小的那個對齊,這樣可以最小化長度。如在32bit的機器上,int的大小為4,因此int儲存的位置都是4的整數倍的位置開始儲存。
- 複雜型別(如結構)的預設對齊方式是它最長的成員的對齊方式
- 結構體對齊後的長度必須是成員中最大的對齊引數(PPB)的整數倍,這樣在處理陣列時可以保證每一項都邊界對齊。
- 結構體作為資料成員的對齊規則:在一個struct中包含另一個struct,內部struct應該以它的最大資料成員大小的整數倍開始儲存。struct A 中包含 struct B, struct B 中包含資料成員 char, int, double,則 struct B應該以sizeof(double)=8的整數倍為起始地址。(感覺跟2優點像,分不清)
struct A
{
char a; //記憶體位置: [0]
double b; // 記憶體位置: [8]...[15]
int c; // 記憶體位置: [16]...[19] ---- 規則1
}; // 記憶體大小:sizeof(A) = (1+7) + 8 + (4+4) = 24, 補齊[20]...[23] ---- 規則3
struct A
{
char a; //記憶體位置: [0]
double b; // 記憶體位置: [8]...[15]
int c; // 記憶體位置: [16]...[19] ---- 規則1
}; // 記憶體大小:sizeof(A) = (1+7) + 8 + (4+4) = 24, 補齊[20]...[23] ---- 規則3
共用體和大小端
- 大端模式:記憶體地址從低地址開始讀,
- 小端模式:記憶體地址從高地址開始讀。
- 陣列地址:地址從低地址開始。
STM32微控制器的儲存方式為小端模式
首先我們要明白,什麼是大端,什麼是小端。拿二進位制的1來舉例,當它在機器中儲存時會轉換成‘00000000000000000000000000000001’
#include <stdio.h>
union UN
{
int a;
char c;
};
int main()
{
union UN un;
un.a = 1;
if (un.c == 1)
printf("小端儲存");
else
printf("大端儲存");
return 0;
}
#include <stdio.h>
int main()
{
int val = 1;
char *ret = (char *)&val;
if (*ret == 1)
printf("小端儲存");
else
printf("大端儲存");
return 0;
}
指標函式與函式指標的區別
- 指標函式是指帶指標的函式,即本質是一個函式,函式返回型別是某一型別的指標
int *f(x,y);
- 函式指標是指向函式的指標變數,即本質是一個指標變數
int (*f) (int x); /*宣告一個函式指標 */
f=func; /* 將func函式的首地址賦給指標f */
- 把函式的地址賦值給函式指標,可以採用下面兩種形式:
fptr=&Function;
fptr=Function;
取地址運算子&不是必需的,因為單單一個函式識別符號就標號表示了它的地址,如果是函式呼叫,還必須包含一個圓括號括起來的引數表。
- 可以採用如下兩種方式來通過指標呼叫函式:
x=(*fptr)();
x=fptr();
第二種格式看上去和函式呼叫無異。但是有些程式設計師傾向於使用第一種格式,因為它明確指出是通過指標而非函式名來呼叫函式的。
下面舉一個例子:
void (*funcp)();
void FileFunc();
void EditFunc();
main()
{
funcp=FileFunc;
(*funcp)();
funcp=EditFunc;
(*funcp)();
}
void FileFunc()
{
printf(FileFunc\n);
}
void EditFunc()
{
printf(EditFunc\n);
}
程式輸出為:
FileFunc
EditFunc
請寫出bool flag 與“零值”比較的if 語句
【標準答案】if ( flag ) if ( !flag )
請寫出float x 與“零值”比較的if 語句:
【標準答案】
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON)
不可將浮點變數用“==” 或“!=” 與數字比較,應該設法
轉化成“>=” 或“<=” 此類形式。
請寫出char *p 與“零值”比較的if 語句
【標準答案】
if (p == NULL) if (p != NULL)
用變數a 給出下面的定義
e) 一個有10個指標的陣列,該指標是指向一個整型數
的;
f) 一個指向有10個整型數陣列的指標;
g) 一個指向函式的指標,該函式有一個整型引數並返
回一個整型數;
h) 一個有10個指標的陣列,該指標指向一個函式,該
函式有一個整型引數並返回一個整型數;
【標準答案】
e)int * a[10];
f)int (*a)[10]
g)int (*a)(int);
h) int (*a[10])(int)
設有以下說明和定義:
typedef union {long i; int k[5]; char c;} DATE;
struct data { int cat; DATE cow; double dog;} too;
DATE max;
則語句
printf("%d",sizeof(struct date)+sizeof(max));
的執行結果是:_
【標準答案】DATE是一個union, 變數公用空間. 裡面最
大的變數型別是int[5], 佔用20個位元組. 所以它的大小是
20
data 是一個struct, 每個變數分開佔用空間. 依次為int4 +
DATE20 + double8 = 32.
所以結果是20 + 32 = 52.
當然… 在某些16位編輯器下, int 可能是2位元組,那麼結果
是int2 + DATE10 + double8 = 20
請問以下程式碼有什麼問題:
char* s="AAA";
printf("%s",s);
s[0]='B';
printf("%s",s);
有什麼錯?
【標準答案】”AAA” 是字串常量。s是指標,指向這個
字串常量,所以宣告s的時候就有問題。
cosnt char* s=”AAA”;
然後又因為是常量,所以對是s[0] 的賦值操作是不合法
的。
int (*s[10])(int) 表示的是什麼啊
【標準答案】int (*s[10])(int) 函式指標陣列,每個指標
指向一個int func(intp aram) 的函式。
c和c++ 中的struct有什麼不同?
【標準答案】c和c++ 中struct的主要區別是c中的struct
不可以含有成員函式,而c++ 中的struct可以。c++ 中
struct和class的主要區別在於預設的存取許可權不同,
struct預設為public ,而class預設為private
會出現什麼問題?列印結果是是多少?
void main()
{
char aa[10];
printf(“%d”,strlen(aa));
}
【標準答案】sizeof()和初不初始化,沒有關係,
strlen()和初始化有關,列印結果值未知。
問sizeof(A) = ?
struct A
{
char t:4;
char k:4;
unsigned short i:8;
unsigned long m;
};
【標準答案】8 ,(每個成員分別按自己的對齊位元組數和PPB(指定的對齊位元組數,32位機預設為4)兩個位元組數最小的那個對齊,這樣可以最小化長度)
求sizeof(name1)?
struct name1{
char str;
short x;
int num;
}
【標準答案】8
求sizeof(name2)?
struct name2{
char str;
int num;
short x;
};
【標準答案】12
要對絕對地址0x100000賦值,我們可以用
(unsigned int*)0x100000 = 1234;
那麼要是想讓程式跳轉到絕對地址是0x100000去執行,應該怎麼做
【標準答案】((void ()( ))0x100000 ) ( );
首先要將0x100000強制轉換成函式指標,即:
(void (*)())0x100000
然後再呼叫它:
*((void (*)())0x100000)();
關鍵字volatile有什麼含意? 並給出三個不同的例子
【參考答案】一個定義為volatile的變數是說這變數可
能會被意想不到地改變,這樣,編譯器就不會去假設
這個變數的值了。精確地說就是,優化器在用到這個
變數時必須每次都小心地重新讀取這個變數的值,而
不是使用儲存在暫存器裡的備份。下面是volatile變數
的幾個例子:
1). 並行裝置的硬體暫存器(如:狀態暫存器)
2). 一箇中斷服務子程式中會訪問到的非自動變數
(Non-automatic variables)
3). 多執行緒應用中被幾個任務共享的變數
<> ” “標頭檔案區別
【標準答案】對於#include <filename.h> ,編譯器從
標準庫路徑開始搜尋filename.h ;
對於#include “filename.h” ,編譯器從使用者的工作路
徑開始搜尋filename.h 。
佇列和棧有什麼區別?
【標準答案】佇列先進先出,棧後進先出。
用巨集定義寫出swap(x,y),即交換兩數
標準答案】
#define swap(x, y) (x)=(x)+(y);(y)=(x)–(y);(x)=(x)–(y);