1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <stdio.h> #include <linux/types.h> int main (int argc, char *argv[]) { __u16 b; __u8 a[3]; b = 102; if (argc <= 1) { printf ("Please provide 1 integer as arguments\n"); return -1; } if (sscanf (argv[1], "%d", &a[0]) != 1) { printf ("Wrong argument: %s\n", argv[1]); return -1; } printf ("a[0] = %d, b = %d\n", a[0], b); } |
先看執行結果:
1 2 | $ ./test 10 a[0] = 10, b = 0 |
從程式描述可知預期輸出結果應該是b = 102
但問題在哪呢?如果有試著編譯上述程式碼還會發現編譯時,編譯器早發覺不對勁而提出警告了:
1 2 3 | $ gcc -o test test.c test.c: In function ‘main’: test.c:16:2: warning: format ‘%d’ expects argument of type ‘int *’, but argument 3 has type ‘__u8 *’ [-Wformat] |
根據編譯器提出的警告,可以有兩種修正方法:
第一種,讓編譯器開心但會徹底把臭蟲埋入程式且難以發現:
既然編譯器預期%d修飾符應該給對應一個int *類別變數,那就改成
1 2 3 4 | if (sscanf (argv[1], "%d", (int *)&a[0]) != 1) { printf ("Wrong argument: %s\n", argv[1]); return -1; } |
改完後看來編譯器滿意了,沒有丟出任何警告執行應該就沒問題吧,結果發現結局一樣,這時不禁開始懷疑sscanf這個函數是不是有bug阿?
翻閱sscanf手冊的Conversions區段有以下描述:
1 2 3 4 5 6 7 8 9 10 | Conversions The following type modifier characters can appear in a conversion spec‐ ification: h Indicates that the conversion will be one of d, i, o, u, x, X, or n and the next pointer is a pointer to a short int or unsigned short int (rather than int). hh As for h, but the next pointer is a pointer to a signed char or unsigned char. |
還沒看出端倪嗎?沒關係,再看看下面增加幾行印出變數位址的程式內容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <stdio.h> #include <linux/types.h> int main (int argc, char *argv[]) { __u16 b; __u8 a[3]; b = 102; if (argc <= 1) { printf ("Please provide 1 integer as arguments\n"); return -1; } if (sscanf (argv[1], "%d", (int *)&a[0]) != 1) { printf ("Wrong argument: %s\n", argv[1]); return -1; } printf ("a[0] = %d, b = %d\n", a[0], b); printf ("Address of a[0]: %p\n", &a[0]); printf ("Address of b: %p\n", &b); printf ("Size of int: %d\n", sizeof (int)); } |
執行結果如下:
1 2 3 4 5 | $ ./test 10 a[0] = 10, b = 0 Address of a[0]: 0xbfc058cb Address of b: 0xbfc058ce Size of int: 4 |
發現變數b距離a[0]只有3 bytes,且得知在此系統環境下一個int長度是4 bytes,此時是否恍然大悟?原來sscanf使用%d修飾字要求把argv[1]字串解析成數值為int並且存入a[0],但a[0]實際只有1 bytes呢!多出來的3 bytes就把記憶體後面連續的空間也改蓋掉了,此時變數b的內容就給填為0了。
第二種修正方法:
回憶剛剛貼的手冊說明內容,既然後面承接的變數長度為8 bits,則應該將程式內容改為:
1 2 3 4 | if (sscanf (argv[1], "%hhd", &a[0]) != 1) { printf ("Wrong argument: %s\n", argv[1]); return -1; } |
得到結果也符合預期且編譯器也不再哇哇叫,皆大歡喜!
Keywords: C/C++、Variable Alignment
沒有留言:
張貼留言