2015年11月28日 星期六

[books] expert c programming (C 专家编程) / c trap and pitfalls (C 陷阱与缺陷)

expert c programming (C 专家编程)



其實不知道該怎麼談論這本書, 所以我決定用幾個程式碼來引起大家對這本書的興趣。

你知道這些指標要怎麼寫才對嗎?

ma.c
 1 #include <stdio.h>
 2 
 3 void my_func1(int fruit[2][3][5]){;}
 4 void my_func2(int fruit[][3][5]){;}
 5 void my_func3(int (*fruit)[3][5]){;}
 6 
 7 int main(int argc, const char *argv[])
 8 {
 9   int apricot[2][3][5];
10   printf("apricot addr: %p\n", apricot);
11   my_func1(apricot);
12   my_func2(apricot);
13   my_func3(apricot);
14 
15   int (*p)[3][5] = apricot;
16   my_func1(p);
17   my_func2(p);
18   my_func3(p);
19 
20   int (*q)[2][3][5] = &apricot;
21   my_func1(*q);
22   my_func2(*q);
23   my_func3(*q);
24 
25   int (*r)[5] = apricot[0];
26   printf("apricot[0] addr: %p\n", apricot[0]);
27   printf("apricot[1] addr: %p\n", apricot[1]);
28   printf("r addr: %p\n", r);
29   int (*r1)[5] = &apricot[0][0];
30   printf("r1 addr: %p\n", r1);
31   r = apricot[1];
32 
33   int *t = apricot[0][1];
34   t = apricot[1][2];
35 
36 
37   return 0;
38 }

31   r = apricot[1];
我還沒想透 ..., 看了 C 陷阱与缺陷已經想通。

使用了 c 標準程式庫的函式名稱會怎樣?
interpositioning.c
 1 //#include <stdio.h>
 2 
 3 void printf(const char *format, ...)
 4 {
 5   puts("interposition printf");
 6 }
 7 int main(int argc, const char *argv[])
 8 {
 9   printf("a\n");
10   return 0;
11 }

真奇怪, 竟然不會 link error! 我不知道 C 標準程式庫怎麼做到, 不過我知道怎麼做可以達到這樣的效果, 使用 weak symbol。

i.c
 1 #include <stdio.h>
 2 
 3 __attribute__((weak))
 4 int func1()
 5 {
 6   return 1;
 7 }
 8 
 9 int main(int argc, const char *argv[])
10 {
11   printf("%d\n", func1());
12   return 0;
13 }

j.c
1 
2 int func1()
3 {
4   return 2;
5 }

descent@debian64:tmp$ gcc i.c j.c -o i
descent@debian64:tmp$ ./i
2

i.c 的 func1 是 weak symbol, 所以呼叫的 func1 是 j.c 的 func1, 類似 interpositioning 的效果。


gcc 1.c 2.c => a.out
執行結果在我的平台是
newdef f = 0.000000, char = 3
對這個結果很驚訝嗎?

1.c
1 int main(int argc, const char *argv[])
2 {
3 //int newdef(float d, char i);
4   float d = 10.0;
5   char j = 3;
6   newdef(d, j);        
7   return 0;
8 }

2.c
1 #include <stdio.h>
2 
3 int newdef(float d, char i)
4 {
5   printf("newdef f = %f, char = %x\n", d, i);
6 }

書中在探討 C 語言中一般書上較少提到的主題, 也會有些和 os 相關的部份; 和 os 相關部份可能是比較難理解的章節 (有實際完成 os 才比較能體會), 但其他部份都是看過就可以理解。指標和陣列的異同非常實用, 費點腦力研讀會很有收穫。

char * const *(*next)();
void(*(*papf)[3])(char *);

上面的語法有難倒你嗎? 3.3 3.4 在討論如何看這種令人腦袋打結的語法。

p219 介紹多維數組陣列的記憶體 layout,

char pea[5][2] 是可以有 5 個長度為 2 的字串? 還是有 2 個長度為 5 的字串?
答案是這樣: 有 5 個長度為 2 的字串
char pea[5][2] = {"a", "b", "c", "d", "e"};
所以我要怎麼指向這個多維陣列?
char (*p)[2] = pea;
我得告訴 c compiler, p++ 一次要移動 2 bytes, 還撐的住, 來個 3 維陣列看看。

char a3[4][3][2];
2 個的有 3 個, 2 個的 3 個有 4 個。
ww ww ww xx xx xx  yy yy yy zz zz zz

指向 2 個的:
char (*p2)[2] = &a3[1][2];

指向包了 2 個的那 3 個:
char (*p3)[3][2] = &a3[2] ;

以下是測試程式碼, 我相信我應該很快會忘記, 所以我寫了這篇。

a.c
 1 #include <stdio.h>
 2 int main(int argc, char *argv[])
 3 {
 4   char pea[5][2] = {"a", "b", "c", "d", "e"};
 5   char (*p)[2] = pea;
 6   int i=0;
 7   for (i=0 ; i < 5 ; ++i)
 8     printf("%s\n", pea[i]);
 9 
10   printf("===\n");
11 
12   for (i=0 ; i < 5 ; ++i)
13   {
14     printf("%s\n", *p++);
15   }
16 
17   char a3[4][3][2] = {"a","b","c", "d","e","f", "l","m","n",  "x","y","z"};
18   char (*p2)[2] = &a3[1][2];
19   printf("%s\n", p2);
20   char (*p3)[3][2] = &a3[2] ;
21   for (i=0 ; i < 3 ; ++i)
22   {
23     printf("%s\n", (*p3)[i]);
24   }
25   return 0;
26 }

執行結果:
descent@descent-u:tmp$ ./a.out 
a
b
c
d
e
===
a
b
c
d
e
f
l
m
n

a.6 的問題很有趣, 如何判斷一個變數是 unsigned/signed, 這可不是單純的面試題目, 在 printf 中就會用到這個了。

s.c
 1 #include "type.h"
 2
 3 #include <stdio.h>
 4
 5 int main(int argc, char *argv[])
 6 {
 7   u16 a = 0xffff;
 8   s16 a1 = 0xffff;
 9   printf("a: %d\n", a);
10   printf("a1: %d\n", a1);
11   return 0;
12 }

result:
a: 65535
a1: -1

printf 知道 0xffff 要印出 65535 和 -1, 所以在這之前, printf 得先知道 0xffff 到底代表 65535 還是 -1, 你有什麼好辦法嗎?

printf 不是用這個方式印出 65535 和 -1, 而是在作 integer promotion 時, ffff -> 0000ffff, 或是 ffff -> ffffffff。

這篇提出了 ansi 的解答版本, 我想了老半天實在想不到答案, 原來要用這招, 厲害。

stackoverflow 的回答沒有比較高竿, 在這個問題上, 很多是沒有幫助或是錯的答案, 用我生硬的英文看了幾篇回文之後, 終於看到這個答案。
#define ISUNSIGNED(a) (a>=0 && ((a=~a)>=0 ? (a=~a, 1) : (a=~a, 0)))

另外一個答案:
#define ISUNSIGNED(a) (a >= a-a && ~a >= a-a)
是錯的, 我覺得很奇怪, 提出答案的人都不先試試看的嗎? 把這行輸入編輯器一點也不難阿? 這答案被下面一位網友吐嘈 (Artefacto: http://stackoverflow.com/questions/7469915/value-vs-type-code-to-determine-if-a-variable-is-signed-or-not), 根本沒有用。

書中的答案:
#define ISUNSIGNED(type) ((type)0 - 1 > 0)

本書翻譯的不是很好, 詞句有些不順暢, 讀來詰屈聱牙, 雖然薄薄一本, 還是得花點心力看完, 下一本就好多了。

c trap and pitfalls (C 陷阱与缺陷)



這本的翻譯好一點, 讀來稍微不那麼痛苦; 也是類似的書籍, 探討 c 語言的陷阱。

b.c
 1 #include <stdio.h>
 2 
 3 int main(int argc, const char *argv[])
 4 {
 5   int c;
 6 
 7   char buf[BUFSIZ];
 8   setbuf(stdout, buf);
 9 
10   while((c=getchar()) != EOF)
11     putchar(c);
12   return 0;
13 }

上面的程式錯在那呢?令人防不勝防。

我之前都是透過 c++ 書籍學習 c  語言, 雖然可以應付工作的需求, 但看了這兩本才知道 c 語言的搞怪 (台語發音)。

setbuf函数详解 p87 上的內容, 一字不差, 怎麼有人好意思把書上的東西寫進自己的 blog, 還沒有註明引用來源。

3.6 邊界計算與不對稱邊界討論值的範圍, 說明 0<=N < 6 比 0<= N <= 5 讓我記住怎麼計算邊界, 在也不會搞錯了, 難怪 stl container 也是用這樣的設計, 值得一讀。

台灣的出版社應該要出版這兩本, 這是可以長賣的書籍, 又很薄, 可以減少製作的時間, 不需要侯捷這樣等級的翻譯作家應該就可以製作的比中國簡體版好, 我認為有這樣程度的 c 語言譯者比較好找, 當然價格也不能定得太貴, 要不然又沒吸引力, 出版社難做阿!

以 C 陷阱与缺陷來說, 人民幣 30, 若是台灣定價在 200 ~ 250 NT 應該可讓台灣讀者接受, 當然我不知道出版社能不能接受。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。