極客C Puzzles。
這里有常見的錯誤,也有易混淆的知識點,還有奇妙的算法,更有那些變態的用法。
原題目都在這里:http://www.gowrikumar.com/c/,下面詳細給出我的解答。
1.sizeof操作符返回值為size_t型,在windows/linux下為typedef size_t unsigned int,而int和unsigned int進行比較時,編譯器會自動把int轉換為unsigned int,又因為int d = -1,所以轉換之后溢出為正值4294967295,條件不成立。
2.這道題命很簡單,仔細看一下就會發現OS_HP-UX_print()中間那個字符“-”,變量和函數名只能由字母、數字還有下劃線組成。
3.continue語句導致最近的循環語句的當前迭代結束,執行權被傳遞給條件計算部分。SO,只會打印一個1。
4.stdout換行緩沖,或者等緩沖區滿了一并輸出,而stderr無緩沖,即時輸出,故一開始是看不到hello-out的。
5.這涉及到一個宏展開的問題。先來講一下宏展開的步驟,以#define MACRO_TEST(x) x為例,如果宏帶有參數,例如x成為形參,調用的時候例如MACRO_TEST(z),那么z則成為實參:
第一步,實參替換形參,帶入宏文本中;
第二步,如果實參也是宏,則繼續展開該實參宏;
第三步,繼續展開宏文本,如果文本中還有宏,繼續展開,否則宏展開順利結束。
上面第一步中,有一個特例,即字符串化預處理符#,如果遇到它,停止后面的宏展開,而是把這個參數當成字符串處理。
SO,這一題就好解答了。第一個輸出完全展開宏,為12,而第二個不完全展開,為f(1,2)。
6.很簡單,int和const char怎么比?//錯誤
正確的應該是default拼寫錯了。。。這個case語法上是對的,我理解成語義了,非常感謝網友們的指正。
7.
因為我的PC是32位的,所以無法測試,google一把,發現和CPU架構有關,IA-64是RISC,不能訪問未對齊的地址。
8.和第13題一樣,解法不同,結果相同。
9.浮點數不能直接比較。
10.int a=1,2;這句被編譯器認為是聲明定義變量,而不是逗號表達式,2是常量,不能為變量名,非法。
11.printf返回值是實際輸出值的個數,SO,值為4321。
12.這個看到register關鍵字,想來是底層應用,google之果然,duff's device,大大的有名。詳細可以看這個帖子的鏈接。
13.每次保留那些不是最后一位的1,循環幾次,就是有幾個1。這個函數針對unsigned int,其實對int也是適用的。
14、函數空參數列表在C語言中表示不定參數,僅有一個void表示沒有參數,SO,program 1是編譯不過的。注意:C++中給前面兩個函數傳參數都是非法的。
15、這個值得好好研究,首先來看一下第一個輸出匯編代碼(MS V8生成)。

發現沒有,頭4個字節(紅框部分),printf("%d")打印頭四個字節,故而輸出為0。
第二個輸出就簡單了,將變量a的數據強制為int輸出。
16、類型T的指針和類型T的數組并非同種類型,程序試圖寫入0X00000004內存,崩潰。詳細可以參考CCFAQ-0.9.4 ch6.1和ch6.2。
17、switch跳過第一個case或default之前的代碼,輸出結果不定。C++編譯器(G++、VC)是編譯不過的,提示a的初始化被case/default標簽跳過了。
18、數組做參數傳遞時會退化成指針,函數void size(int arr[SIZE])等價于void size(int *arr),輸出為4。
19、如果傳給Error函數的參數字串包含格式化字符串怎么搞?例如,"test123...%d",那么輸出可能為"test123...1245032",大大超出預期。
20、輸入a,按回車,輸出a,換行,換行,然后程序結束。這說明第二個scanf("%c", &c),這樣不包含空格,直接讀入了回車字符。當然,有空格是不讀的。原因?C99 7.19.6.2第五條說的狠清楚:包含空白符的指令從輸入中讀取第一個非空白符字符,知道無字符可讀。
21、數組溢出。
22、輸出10,4,10,因為sizeof操作符實在編譯時決定的,里面的i++不會起作用。
這里補充一下,sizeof('a')=?,C99中將'a'理解為整形字符常量,為int;C++中為字符字面量,為char。
23、指針類型不匹配。
24、不要看到逗號表達式就激動,等號優先級高于逗號,所以輸出1,不是3。如果改成i=(1,2,3)則是3。
25、可能死循環、除數為0。
26、
不懂,以后有時間在研究
27、參考第5題。
28、算法原理,讓兩個int數字互掐,直到兩人相等為止。如果是4個數求結果就是4強淘汰賽,分兩隊互掐,掐完的勝利者再互掐,呵呵。scanf返回的是輸入的字符數目。
29、其實y=y/*p是注釋語句。。。
30、同20題,scanf讀取非‘-’字符。
31、scanf("%d\n", &n);
32、加號的優先級高于左移運算符。
33、三目運算符?:中不能使用return
34、i--一直小于20,循環到溢出為止。更改: n--。
35、ptr2是一個int型變量,不是指針。聲明指針的最后方法是讓*緊挨著放在變量名前面,例如:int *ptr1, *ptr2。
36、a沒被初始化就運算也就算了,盡然還有除0這種嚴重錯誤。
37、條件表達式一旦一個不成立,立即轉到下一個獨立條件判斷,匯編碼可以很好的看出。打印8,不是9,也不是7。
38、指針a已經指導最后了,free崩潰。
39、這個題目貌似很變態
,別怕,還是拿出匯編來看問題。
在來看,說明&b["junk/super"]相當于下面的這兩行代碼:
第二行printf中的1["this"]其實等價于"this"[1],即取出字符'h'。下面在來一次測試:
40、讀取字符知道遇到a為止,參見C99 7.19.6.2第12條。
41、a.c中的為聲明,b.c中的為定義,main.c中的為外聯聲明,需要找定義,所以鏈接b.c中的變量定義,與a.c中的無關。
42、閱讀了這題,才明白為什么C那么牛逼
。參考 ccfaq-0.9.4 第2.12問。offsetof(struct s, f)計算s結構中f域的偏移量。
43、SWAP(a++, b++)。
44、同第五題。
45、
46、
看不懂
47、同43.
48、起碼為VarArguments(const char *format, ...),可參考printf函數,不定參數不能只有這一個...。因為標準C要求用可變參數的函數至少有一個固定參數項,這樣才可以使用va_start()。
補充:標準C++中可以這樣定義,表示0個或多個參數,例如void fun(...);函數void fun(void)和函數void fun()意思完全一樣,和C不同,見14題。
49、
還真沒想出來。。。
50、讀取format字符串的長度存入變量中,例如printf("hello%n", &a),a的值為5。
51、
52、%%
53、const char *p指向const char的非const指針,char* const p指向char的const指針。
54、memcopy可以重疊,而后者不可以。
55、%lf打印double,%f打印float
56、
57、
這里有常見的錯誤,也有易混淆的知識點,還有奇妙的算法,更有那些變態的用法。
原題目都在這里:http://www.gowrikumar.com/c/,下面詳細給出我的解答。
1.sizeof操作符返回值為size_t型,在windows/linux下為typedef size_t unsigned int,而int和unsigned int進行比較時,編譯器會自動把int轉換為unsigned int,又因為int d = -1,所以轉換之后溢出為正值4294967295,條件不成立。
2.這道題命很簡單,仔細看一下就會發現OS_HP-UX_print()中間那個字符“-”,變量和函數名只能由字母、數字還有下劃線組成。
3.continue語句導致最近的循環語句的當前迭代結束,執行權被傳遞給條件計算部分。SO,只會打印一個1。
4.stdout換行緩沖,或者等緩沖區滿了一并輸出,而stderr無緩沖,即時輸出,故一開始是看不到hello-out的。
5.這涉及到一個宏展開的問題。先來講一下宏展開的步驟,以#define MACRO_TEST(x) x為例,如果宏帶有參數,例如x成為形參,調用的時候例如MACRO_TEST(z),那么z則成為實參:
第一步,實參替換形參,帶入宏文本中;
第二步,如果實參也是宏,則繼續展開該實參宏;
第三步,繼續展開宏文本,如果文本中還有宏,繼續展開,否則宏展開順利結束。
上面第一步中,有一個特例,即字符串化預處理符#,如果遇到它,停止后面的宏展開,而是把這個參數當成字符串處理。
SO,這一題就好解答了。第一個輸出完全展開宏,為12,而第二個不完全展開,為f(1,2)。
6.很簡單,int和const char怎么比?//錯誤
正確的應該是default拼寫錯了。。。這個case語法上是對的,我理解成語義了,非常感謝網友們的指正。
7.

8.和第13題一樣,解法不同,結果相同。
9.浮點數不能直接比較。
10.int a=1,2;這句被編譯器認為是聲明定義變量,而不是逗號表達式,2是常量,不能為變量名,非法。
11.printf返回值是實際輸出值的個數,SO,值為4321。
12.這個看到register關鍵字,想來是底層應用,google之果然,duff's device,大大的有名。詳細可以看這個帖子的鏈接。
13.每次保留那些不是最后一位的1,循環幾次,就是有幾個1。這個函數針對unsigned int,其實對int也是適用的。
14、函數空參數列表在C語言中表示不定參數,僅有一個void表示沒有參數,SO,program 1是編譯不過的。注意:C++中給前面兩個函數傳參數都是非法的。
15、這個值得好好研究,首先來看一下第一個輸出匯編代碼(MS V8生成)。
float a = 12.5;
00411576 fld dword ptr [__real@41480000 (4156C0h)] //將立即數放入浮點寄存器ST0
0041157C fstp dword ptr [a] //將ST0中的數據放入變量a中
printf("%d\n", a);
0041157F fld dword ptr [a] //再一次將變量a中的數據放入ST0
00411582 mov esi,esp //保存棧頂
00411584 sub esp,8 //調整棧頂指針ESP,注意這里調整的是8個字節,不是float的4個,說明內部按double轉換了
00411587 fstp qword ptr [esp] //將ST0中的數據入棧
0041158A push offset string "%d\n" (415640h) //printf函數參數入棧
0041158F call dword ptr [__imp__printf (4182B8h)] //調用printf
00411595 add esp,0Ch //棧頂恢復
00411587 fstp qword ptr [esp]這句代碼之后,我們來看看剛剛入棧的內容,這時候ESP=0012FE7C,查看內存,如下圖00411576 fld dword ptr [__real@41480000 (4156C0h)] //將立即數放入浮點寄存器ST0
0041157C fstp dword ptr [a] //將ST0中的數據放入變量a中
printf("%d\n", a);
0041157F fld dword ptr [a] //再一次將變量a中的數據放入ST0
00411582 mov esi,esp //保存棧頂
00411584 sub esp,8 //調整棧頂指針ESP,注意這里調整的是8個字節,不是float的4個,說明內部按double轉換了
00411587 fstp qword ptr [esp] //將ST0中的數據入棧
0041158A push offset string "%d\n" (415640h) //printf函數參數入棧
0041158F call dword ptr [__imp__printf (4182B8h)] //調用printf
00411595 add esp,0Ch //棧頂恢復

發現沒有,頭4個字節(紅框部分),printf("%d")打印頭四個字節,故而輸出為0。
第二個輸出就簡單了,將變量a的數據強制為int輸出。
16、類型T的指針和類型T的數組并非同種類型,程序試圖寫入0X00000004內存,崩潰。詳細可以參考CCFAQ-0.9.4 ch6.1和ch6.2。
17、switch跳過第一個case或default之前的代碼,輸出結果不定。C++編譯器(G++、VC)是編譯不過的,提示a的初始化被case/default標簽跳過了。
18、數組做參數傳遞時會退化成指針,函數void size(int arr[SIZE])等價于void size(int *arr),輸出為4。
19、如果傳給Error函數的參數字串包含格式化字符串怎么搞?例如,"test123...%d",那么輸出可能為"test123...1245032",大大超出預期。
20、輸入a,按回車,輸出a,換行,換行,然后程序結束。這說明第二個scanf("%c", &c),這樣不包含空格,直接讀入了回車字符。當然,有空格是不讀的。原因?C99 7.19.6.2第五條說的狠清楚:包含空白符的指令從輸入中讀取第一個非空白符字符,知道無字符可讀。
21、數組溢出。
22、輸出10,4,10,因為sizeof操作符實在編譯時決定的,里面的i++不會起作用。
這里補充一下,sizeof('a')=?,C99中將'a'理解為整形字符常量,為int;C++中為字符字面量,為char。
23、指針類型不匹配。
24、不要看到逗號表達式就激動,等號優先級高于逗號,所以輸出1,不是3。如果改成i=(1,2,3)則是3。
25、可能死循環、除數為0。
26、

27、參考第5題。
28、算法原理,讓兩個int數字互掐,直到兩人相等為止。如果是4個數求結果就是4強淘汰賽,分兩隊互掐,掐完的勝利者再互掐,呵呵。scanf返回的是輸入的字符數目。
29、其實y=y/*p是注釋語句。。。
30、同20題,scanf讀取非‘-’字符。
31、scanf("%d\n", &n);
32、加號的優先級高于左移運算符。
33、三目運算符?:中不能使用return
34、i--一直小于20,循環到溢出為止。更改: n--。
35、ptr2是一個int型變量,不是指針。聲明指針的最后方法是讓*緊挨著放在變量名前面,例如:int *ptr1, *ptr2。
36、a沒被初始化就運算也就算了,盡然還有除0這種嚴重錯誤。
37、條件表達式一旦一個不成立,立即轉到下一個獨立條件判斷,匯編碼可以很好的看出。打印8,不是9,也不是7。
38、指針a已經指導最后了,free崩潰。
39、這個題目貌似很變態

printf(&a["ya!hello! how is this ? %s\n"], &b["junk/super"]);
00413734 mov eax,dword ptr [b]
00413737 add eax,offset string "junk/super" (415994h) //eax指向string "junk/super"的第5個字符
0041373C mov esi,esp
0041373E push eax
0041373F mov ecx,dword ptr [a]
00413742 add ecx,offset string "ya!hello! how is this ? %s\n" (415A80h) //ecx指向string 的第3個字符
00413748 push ecx
00413749 call dword ptr [__imp__printf (4182B8h)]
由匯編碼來分析,其實相當于那一句C代碼等價于printf("hello! how is this ? %s\n", "super")。00413734 mov eax,dword ptr [b]
00413737 add eax,offset string "junk/super" (415994h) //eax指向string "junk/super"的第5個字符
0041373C mov esi,esp
0041373E push eax
0041373F mov ecx,dword ptr [a]
00413742 add ecx,offset string "ya!hello! how is this ? %s\n" (415A80h) //ecx指向string 的第3個字符
00413748 push ecx
00413749 call dword ptr [__imp__printf (4182B8h)]
在來看,說明&b["junk/super"]相當于下面的這兩行代碼:
char arr[] = "junk/super";
const char *p = &arr[b];
即數組a[n]等價于n[a]。const char *p = &arr[b];
第二行printf中的1["this"]其實等價于"this"[1],即取出字符'h'。下面在來一次測試:
int a[] = {4, 5, 6, 7, 8};
int b = 3[a];
printf("%d\n", b);//輸出7
原理參考C99 6.5.2.1第二條,E1[E2]的操作下表[]被解析為(*((E1)+(E2))),so,上面的代碼就徹底清楚了。int b = 3[a];
printf("%d\n", b);//輸出7
40、讀取字符知道遇到a為止,參見C99 7.19.6.2第12條。
41、a.c中的為聲明,b.c中的為定義,main.c中的為外聯聲明,需要找定義,所以鏈接b.c中的變量定義,與a.c中的無關。
42、閱讀了這題,才明白為什么C那么牛逼

43、SWAP(a++, b++)。
44、同第五題。
45、
int is_overflow(int *p, int a, int b)
{
long x, y, z;
x = (long)a;
y = (long)b;
*p = a + b;
z = (long)((unsigned long)x + y);
if ( (z^x) >= 0 || (z^b) >= 0 )
return 1;
else
return 0; //溢出
}
{
long x, y, z;
x = (long)a;
y = (long)b;
*p = a + b;
z = (long)((unsigned long)x + y);
if ( (z^x) >= 0 || (z^b) >= 0 )
return 1;
else
return 0; //溢出
}
46、

47、同43.
48、起碼為VarArguments(const char *format, ...),可參考printf函數,不定參數不能只有這一個...。因為標準C要求用可變參數的函數至少有一個固定參數項,這樣才可以使用va_start()。
補充:標準C++中可以這樣定義,表示0個或多個參數,例如void fun(...);函數void fun(void)和函數void fun()意思完全一樣,和C不同,見14題。
49、

50、讀取format字符串的長度存入變量中,例如printf("hello%n", &a),a的值為5。
51、
mov eax, [a]
add eax, [b]
add eax, [b]
52、%%
53、const char *p指向const char的非const指針,char* const p指向char的const指針。
54、memcopy可以重疊,而后者不可以。
55、%lf打印double,%f打印float
56、
short int a = 0x00ff;
char *p = (char *)&a;
if (*p == 0xff)
printf("big endia");
else
printf("small endia");
char *p = (char *)&a;
if (*p == 0xff)
printf("big endia");
else
printf("small endia");
57、
int main()
{
if (printf("hello world") < 0)
{}
}
{
if (printf("hello world") < 0)
{}
}