這裡要探討的是修飾子可能導致的臭蟲,很容易埋入的臭蟲而且難以找出,直接看下面這段程式:
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