以下這些題目是我曾遇到過的,覺得有深度的問題,對題目的解析絕大部分是本人的思考(引用的已注明出處),可能有不對的地方,希望大家不吝指正。
(PS:如果您對以下題目表示無壓力,只能說您對C有一定的理解,至于是否精通C,不能由此文判斷。)參考答案在文末。
1★先來個簡單的:
~~~
#include <stdio.h>
int main(void)
{
int a[3][2] = { (0,1), (2,3), (4,5) } ;
int *p ;
p = a[0] ;
printf(“%d”, p[0] ) ;
}
~~~
仔細看看花括號里面嵌套的是小括號,而不是花括號。即這花括號里嵌套了逗號表達式。(這考的是眼力^_^)
2★?int a[10]; 問下面哪些不可以表示 a[1] 的地址?
A. a+sizeof(int) ? B. &a[0]+1 ? ?C. (int*)&a+1 ? ?D. (int*)((char*)&a+sizeof(int))
//Tecent某年實習生筆試題目
此題對于理解了指針與數組的同學來說,很easy。(關于數組和指針那點事,可瀏覽本博客, [點這里](http://blog.csdn.net/yang_yulei/article/details/8071047))
3★下面的C程序是合法的嗎?如果是,那么輸出是什么?
~~~
#include <stdio.h>
int main()
{
int a=3, b = 5;
printf(&a["Ya!Hello!how is this? %s\n"], &b["junk/super"]);
printf(&a["WHAT%c%c%c %c%c %c !\n"], 1["this"],
2["beauty"],0["tool"],0["is"],3["sensitive"],4["CCCCCC"]);
return 0;
}//來源于酷殼網http://coolshell.cn/articles/945.html
~~~
本例主要展示了一種另類的用法。下面的兩種用法是相同的:
“hello”[2]
2["hello"]
如果你知道:a[i] 其實就是 *(a+i)也就是 *(i+a),所以如果寫成 i[a] 應該也不難理解了。
4★?32 位機上根據下面的代碼,問哪些說法是正確的?(多選題類型)
~~~
signed char a = 0xe0;
unsigned int b = a;
unsigned char c = a;
~~~
A. a>0 && c>0 為真 ? ? ? B. a == c 為真 ? ? ?C. b 的十六進制表示是:0xffffffe0 ? ? D. 上面都不對
//Tencent某年實習生筆試題目
此題深入地考察了C的類型轉換方式。(此為多選題類型,一般人不敢確定他的答案是正確的)
A 錯:a 是負數,c 是正數,跟 0 比較要轉換到 int。
? ? signed char a 其實也就是char a,其轉換到int負數還是負數(高位填充1)轉換后結果為0xFFFFFFE0
? ? unsigned char c 也為0xE0,但其是正數(signed char轉unsigned char 底層位不變 只是改變了解釋規則)
? ? unsigned char 轉int,正數還是正數(高位填充0)轉換后結果為0x000000E0
B 錯:B錯?首先說 a 和 c 的二進制表示一模一樣,都是 0xe0,那么比較就不相等?!是的。
? ? 一個char型和一個unsigned char比較,其中的類型如何轉換?
? ? C語言的整型提升規則:C的整型算數運算總是至少以缺省整型類型的精度來進行的。為了獲得這個精度,表達式中的字符型和短整型操作數在使用之前被轉換為普通整型。
? ? 本博的早期文章:http://blog.csdn.net/yang_yulei/article/details/8068210
? ? 所以,a == c中,a和c都要先轉換成int型,再比較。有A選項分析知,a轉int型為負數,b轉int型為正數,故它倆不等。
C 對:C對?C 怎么就對了?a 是一個 signed char,賦值給 unsigned int 的 b,前若干個字節不是補 0 嗎?
? ? 但實際情況是:首先 signed char 轉換為 int,然后 int 轉換成 unsigned int,所以最初是符號擴展,然后一個 int 賦值給了 unsigned int(其實還是整型提升規則)
D 不解釋。
至于對這個問題的解釋,我認為(只是我認為,不是權威的解釋)是因為你使用的是32位的操作系統,我們說的操作系統的位數其實值的是CPU GPRs(General-Purpose Registers,通用寄存器)的數據寬度為32位,32位指令集就是運行32位數據的指令,也就是說處理器一次可以運行32bit數據。你傳遞參數的時候輸入指定的格式為%hd,但是壓棧的時候還是壓入了32bit的數據,只不過高位是0。要不然我們為什么經常會說c語言中字節對齊的問題?
5★?下面程序的輸出結果(32位小端機)
~~~
#include <stdio.h>
int main()
{
long long a = 1, b = 2, c = 3;
printf("%d %d %d\n", a, b, c);
return 0;
}
//Tencent某年實習生筆試題目
~~~
//以下是長篇大論
首先,sprintf/fprintf/printf/sscanf/fscanf/scanf等這一類的函數,它們的調用規則(calling conventions)是cdecl,cdecl調用規則的函數,所有參數從右到左依次入棧,這些參數由調用者清除,稱為手動清棧。被調用函數不會要求調用者傳遞多少參數,調用者傳遞過多或者過少的參數,甚至完全不同的參數都不會產生編譯階段的錯誤。函數參數的傳遞都是放在棧里面的,而且是從右邊的參數開始壓棧,printf()是不會對傳遞的參數進行類型檢查的,它只有一個format specification fields的字符串,而參數是不定長的,所以也沒辦法對傳遞的參數做類型檢查,也沒辦法對參數的個數進行檢查。所以了,壓棧的時候,參數列表里的所有參數都壓入棧中了,它不知道有多少個參數,所以它都壓棧。
那么問題來了:編譯器是怎么去定義壓棧的行為的?是先把這longlong類型轉換為int型再壓棧么?還是直接壓棧?
在32位機器上,64位的整數被拆分為兩個32位整數,printf會把64位的按照兩個32的參數來處理。此時printf會認為實際的參數為6個,而不是3個。
c,b,a壓入之后,在最低的12字節處是a和b,a占2*4個bytes,b占1*4個byte。b先壓入棧,a后壓入棧。但是為什么a的布局是這樣的?因為這是little endian,即每個數字的高字節在高地址,低字節在低地址。而棧的內存生長方向是從大到小的,也就是棧底是高地址,棧頂是低地址,所以a的低字節在低地址。(有條件的同學可以在big endian的機器上驗證一下)
那么輸出的時候,format specification fields字符串其匹配棧里面的內容,首先一個%d取出4個bytes出來輸出,然后后面又有一個%d再取出4個bytes出來打印。所以結果就是這樣了。也就是說剛開始壓入棧的b的值在輸出的時候根本都沒有用到。
總結:
printf在壓棧時,對于長度小于32位的參數,自動擴展成32位(由CPU的位數決定的)。
故在根據格式串解釋時,對于%c %hd這樣的小于32位數據的格式串,系統也會自動提取32位數據解釋,而不會提取8位或16位來解釋。(因為你把人家壓入的時候就規定了擴展成32位嘛)
至于浮點參數壓棧的規則:float(4 字節)類型擴展成double(8 字節)入棧。所以在輸入時,需要區分float(%f)與double(%lf),而在輸出時,用%f即可。printf函數將按照double型的規則對壓入堆棧的float(已擴展成double)和double型數據進行輸出。
至于longlong參數的規則:在32位機上,64位整數被拆分為兩個32位整數壓棧,在64位機上不存在這個問題,64位機上本題輸出1,2,3
PS:
關于longlong型的輸出:和平臺和編譯器有關
在**windows下需要用__int64配合%I64d**。而在**UNIX、Linux中必須使用標準C規定的long long配合%lld。**
unsigned __int64b= 9223372036854775808ll
printf("%I64u",b);
6★下面這段代碼會掛么?會掛在哪一行?
~~~
#include <stdio.h>
struct str{
int len;
char s[0];
};
struct foo {
struct str *a;
};
int main(int argc, char**argv) {
struct foo f={0};
if (f.a->s) {
printf( f.a->s);
}
return 0;
}
~~~
//詳細分析請見本博客,[點這里](http://blog.csdn.net/yang_yulei/article/details/23395315)。
7★請問下面的程序的輸出值是什么?
~~~
#include <stdio.h>
#include <stdlib.h>
#define SIZEOF(arr)(sizeof(arr)/sizeof(arr[0]))
#define PrintInt(expr)printf("%s:%d\n",#expr,(expr))
int main()
{
/* The powers of 10*/
int pot[] = {
0001,
0010,
0100,
1000
};
int i;
for(i=0; i<SIZEOF(pot); i++)
PrintInt(pot[i]);
return 0;
}//來源于酷殼網http://coolshell.cn/articles/945.html
~~~
參考答案:好吧,如果你對于PrintInt這個宏有問題的話,你可以百度一下。不過,本例的問題不在這里,其實很簡單了,以C/C++中,以0開頭的數字都是八進制的。
8★請問下面的程序的輸出值是什么?
~~~
#include <stdio.h>
int main(void)
{
char a[1000] ;
int i ;
for(i=0; i<1000; i++)
{
a[i]= -1-i ;
}
printf(“%d”,strlen(a)) ;
return 0 ;
}
~~~
按照負數補碼規則,可知-1的補碼為0xff,當i值為127時,a[127]的值為-128,此時右邊整型轉換后,正好是左邊char型能夠表示的最小負數。當i繼續增加,右邊為-129,對應的十六進制數為0xffffff7f 而char只有8位,故轉換時高位被丟棄 左邊得到0x7f。當i繼續增加到255時,-256的低8位為0。然后當i增加到256時,-257的低8位為0xff 如此又開始一輪的循環。
從上面分析可知:a[0]到a[254]里面的值都不為0,而a[255]的值為0. 故strlen(a)為255
【char默認是有符號的,其表示的值的范圍為[-128,127]】
9★在X86系統下,輸出的值為多少?
~~~
#include <stdio.h>
int main(void)
{
int a[5]={1,2,3,4,5} ;
int* ptr1 =(int *)(&a+1) ;
int* ptr2 =(int *)((int)a+1) ;
printf(“%x,%x”,ptr1[-1], *ptr2) ;
return 0 ;
}
~~~
若對于指針ptr1和ptr2具體指向不明白的,[請點擊這里瀏覽。。](http://blog.csdn.net/yang_yulei/article/details/8071068)
對于ptr[-1]的值為5,沒什么好說的了。
此題主要涉及的是大小端的問題,intel機器一般是小端模式,即:例如對整數來說,內存中的低地址字節存儲整型的低地址部分,內存中的高地址字節存整型的高地址部分。(與我們平時的書寫順序相反)
a[0] a[1]在內存中的存儲為:(地址從小到大增長)0x1000 0000? 0x2000 0000
ptr2指向a[0]的第二個字節處,且它為int型指針,故提取從a[0]的第二個字節開始的后面4個字節。在內存中即為0x10**000000?0x20**00 0000(陰影部分) 打印來為0x2000000
10★
~~~
(*(void(*)( ))0 )( ) 這是什么?
~~~
//可參考:[請點擊這里瀏覽函數指針部分](http://blog.csdn.net/yang_yulei/article/details/8071068)
這個是《C陷阱與缺陷》中的一個例子。
從內層到外層分析:
1,void(*)()這是一個函數指針。 這個函數沒有返回值也沒有參數
2,void(*)()0這是將整型0強制轉換為函數指針類型。(即:0號地址處開始存儲著一段函數)
3,(*(void(*)( ))0 ) 取出0號地址處的函數
4,(*(void(*)( ))0 )( ) 調用0號地址處的函數。
【由此可見 指針和強制類型轉換聯手雙劍合璧威力無窮! 可以實現匯編級的操作】
**【相信程序員,不阻止程序員做他們想做的事】**
11★請問下面的程序的輸出值是什么?
~~~
#include <stdio.h>
int main()
{
int a[5][5] ;
int (*p)[4] ;
p=a ;
printf("%d", &p[4][2]-&a[4][2]) ;
return 0 ;
}
~~~
[詳細分析請點擊](http://blog.csdn.net/yang_yulei/article/details/8071068)
12★
~~~
struct S
{ char c ;
int i[2];
double v ;
} ;
~~~
在windows系統下成員i的偏移量是多少?在Linux系統下i的偏移量是多少?
[//請點擊這里參考](http://blog.csdn.net/yang_yulei/article/details/8072567)
13★下面代碼中有BUG,請找出:
~~~
int tadd_ok(int x, int y) //判斷加法溢出
{
int sum = x+y ;
return (sum-x == y) && (sum-y == x) ;
}
int tsub_ok(int x, int y) //判斷減法溢出
{
return tadd_ok(x, -y) ;
}
//此題來源于等重量黃金價值的書——《深入理解計算機系統》
~~~
有兩個BUG:
1,tadd_ok判斷加法溢出時,無論是否溢出,(x+y)-x都==y,若溢出結果sum再減y 再溢出得x。正確實現應為:分兩種情況——正數運算是否溢出(即兩正數相加為負數),負數運算是否溢出(即兩負數相加為正數)。有兩種情況之一者即判斷溢出。
2,return tadd_ok(x, -y) ;當y為TMIN即int型所能表示的最小值,由于補碼范圍的不對稱性,int的最小值無對應的正值,故y取TMIN時,對其取反會發生溢出,溢出的結果還是TMIN。
這樣就導致判斷結果錯誤。
14★
~~~
編寫一些代碼,確定一個變量是有符號數還是無符號數:
~~~
//來源于《C專家編程》
參考答案:
無符號數的本質特征是它永遠不會是負的。
用宏定義的形式:#define ISUNSIGNED (type)? ( (type)0 –1> 0 )
//也有其它方法實現,不過個人認為這種方法最簡潔。
15★
再來一發關于復雜指針聲明的,這是C中的難點:
寫出變量abc的核心是什么,并用多個typedef改寫下面的聲明式
~~~
int *(*(*(*abc) ( ) ) [6]) ( ) ;
~~~
解析:
abc是一個函數指針,這類函數接收0個參數,返回一個指針,這個指針指向一個具有6個元素的數組,數組里的每個元素是函數指針,這類函數接收0個參數,返回值為int* 類型。abc的定義同下:?
typedef int* (*type1)();
typedef type1 (*type2)[6];
typedef type2 (*type3)();?
type3 abc;
【從內到外解讀聲明,從外到內typedef】
**關于變量的復雜聲明:**
從外到內,層層剝開,先找核心,再向右看。(個人總結的,若有不妥請指正)
找到核心變量后,從右向左讀。
* 讀作”指向…的指針”
[] 讀作”…的數組”
() 讀作”返回…的函數”
簡單的例子:
**int *f() ; ? ? ? ? ? ? // f: 返回指向int型的指針**
步驟:
1)找標識符f:讀作”f是…”
2)向右看,發現”()”讀作”f是返回…的函數”
3)向右看沒有什么,向左看,發現*,讀作”f是返回指向…的指針的函數”
4)繼續向左看,發現int,讀作”f是返回指向int型的指針的函數”
**int (*pf)() ; ? ? ? ? ?// pf是一個指針——指向返回值為int型的函數**
1)標識符pf,讀作“pf是…”
2)向右看,發現),向左看,發現\*,讀作 “pf是指向…的指針”
3)向右看,發現”()”,讀作“pf是指向返回…的函數的指針”
4)向右看,沒有,向左看發現int,讀作”pf是指向返回int型的函數的指針
**部分答案:**
1題、答案應該是1.
2題、答案為A。
3題、本例是合法的,輸出如下:
Hello! how is this? super
That is C !
4題、略
5題、結果為:1,0,2
6題、略
7題、本例的輸出會是:1,8,64,1000
8題、答案是:255
8題、答案:5,2000000
9題、略
10題、略
11題、答案-4
12題、略
13題、略