日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

sscanf函数中类型不匹配警告引发的BUG和思考

發布時間:2025/3/21 编程问答 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 sscanf函数中类型不匹配警告引发的BUG和思考 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

2019獨角獸企業重金招聘Python工程師標準>>>

BUG產生背景

項目開發中的在對網絡的IP等地址進行協議封裝的處理過程中,我使用了如下的一段代碼:

buff[0] = content->res_network_params.up_res_status; sscanf(content->res_network_params.ip,"%d.%d.%d.%d",buff+1,buff+2,buff+3,buff+4); sscanf(content->res_network_params.mask,"%d.%d.%d.%d",buff+5,buff+6,buff+7,buff+8); sscanf(content->res_network_params.gateway,"%d.%d.%d.%d",buff+9,buff+10,buff+11,buff+12); sscanf(content->res_network_params.dns1,"%d.%d.%d.%d",buff+13,buff+14,buff+15,buff+16); sscanf(content->res_network_params.dns2,"%d.%d.%d.%d",buff+17,buff+18,buff+19,buff+20);buff[21] = content->res_network_params.web_port&0xff;buff[22] = (content->res_network_params.web_port>>8)&0xff;buff[23] = content->res_network_params.video_port&0xff;buff[24] = (content->res_network_params.video_port>>8)&0xff;buff[25] = content->res_network_params.rtsp_port&0xff;buff[26] = (content->res_network_params.rtsp_port>>8)&0xff;buff[27] = content->res_network_params.upnp_flag;buff[28] = content->res_network_params.dhcp_flag;

其中buff的數據類型為unsigned char *.這段代碼在編譯時會產生如下一串的警告:

warning: format ‘%d’ expects argument of type ‘int *’, but argument 3 has type ‘unsigned char *’ [-Wformat] ...... 而一開始我因為沒有找到合適的格式符就把這些警告給忽略了,同時也因為當我在X86機器上編寫代碼進行接口測試時他們工作正常,所以沒有給予更多的關注。測試結果之一為: [RE_NET]res = 1,ip:192.168.0.178,mask:255.255.255.0,gw:192.168.0.1,fdns:202.112.20.131,sdns:192.168.0.1 [buff]01 c0 a8 00 b2 ff ff ff 00 c0 a8 00 01 ca 70 14 83 c0 a8 00 01 50 00 54 24 6a 21 00 01

可是當我在開發板(ARM)上運行時,卻得到了完全出乎意料的結果,buff完全不對,結果如下,而且每次都是這個結果:

[RE_NET]res = 1,ip:192.168.0.178,mask:255.255.255.0,gw:192.168.0.1,fdns:202.112.20.131,sdns:192.168.0.1 [buff] 00 00 00 ff 00 00 00 a8 00 00 00 70 00 00 00 a8 00 00 00 01 00 50 00 54 24 6a 21 00 01 此為BUG所在,我對這個初看毫無規律的結果幾乎毫無頭緒,讓我頭疼了一天多。

BUG定位與解決

現象分析

開發板的運行結果奇怪在不僅沒有給buff[1]到buff[20]賦予正確的值,還在于它把buff[0]的值給覆蓋掉了,可是buff[21]往后的端口值卻仍然是正確的。

問題定位

在花了一天的時間check了整個執行流程沒有找到問題后,我才回到上面那段代碼,把5句sscanf調用屏蔽,直接用memset(buff+1,0xfe,20);強制賦值,結果發現在開發板上的結果與x86的測試結果一致,正確了。直到此時我才再次想起那串警告,才猜到是sscanf的格式符類型不匹配導致的內存寫覆蓋問題,于是我寫了一個測試程序來驗證猜測和分析具體的問題產生過程,最終解決了問題。

測試程序的代碼如下:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>int main(int argc, char **argv) {unsigned char buff[32]={0};int ret = 0,i;char *ip = "192.168.0.178";printf("buff addr[0-31] = %p,%p...%p(30),%p(31)\n",buff,buff+1,buff+30,buff+31);/**< 4,12,20,28 */sscanf(ip,"%d.%d.%d.%d",buff+4,buff+12,buff+20,buff+28);printf("buff value[4,12,20,28]:0x%0x,0x%0x,0x%0x,0x%0x\n",buff[4],buff[12],buff[20],buff[28]);printf("buff value[0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n\n");/**< 4,5,6,7*/memset(buff,0,sizeof(buff));sscanf(ip,"%d.%d.%d.%d",buff+4,buff+5,buff+6,buff+7);printf("buff value[4,5,6,7]:0x%0x,0x%0x,0x%0x,0x%0x\n",buff[4],buff[5],buff[6],buff[7]);printf("buff value[0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n\n");/**< 192.168*/memset(buff,0,sizeof(buff));sscanf("192.","%d.",buff+4);printf("buff value[4(192),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n");sscanf("168.","%d.",buff+5);printf("buff value[5(168),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n");sscanf("1.","%d.",buff+6);printf("buff value[6(1),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n");sscanf("178.","%d.",buff+7);printf("buff value[7(178),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n");sscanf("255.","%d.",buff+8);printf("buff value[8(255),0-32]:\n");sscanf("255.","%d.",buff+8);printf("buff value[8(255),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n\n");/**< use inet_addr/inet_network interface convert */in_addr_t addr = inet_addr(ip);printf("addr by inet_addr = [addr]%p,[value]0x%0x, %u\n",&addr, addr, addr);// cast convert address typeunsigned char *paddr = (unsigned char *)&addr;printf("addr by char address: %p,%p,%p,%p\n",paddr,paddr+1,paddr+2,paddr+3);printf("addr by char value: 0x%0x,0x%0x,0x%0x,0x%0x\n",paddr[0],paddr[1],paddr[2],paddr[3]);printf("\n");addr = inet_network(ip);printf("addr by inet_network = [addr]%p,[value]0x%0x, %u\n",&addr, addr, addr);// cast convert address typepaddr = (unsigned char *)&addr;printf("addr by char address: %p,%p,%p,%p\n",paddr,paddr+1,paddr+2,paddr+3);printf("addr by char value: 0x%0x,0x%0x,0x%0x,0x%0x\n",paddr[0],paddr[1],paddr[2],paddr[3]);return 0; } 在PC上和開發板上的測試結果出來后,問題就一目了然了,PC上的結果都符合預期,這里就不粘貼了,開發板上的測試結果為: [root@anyka /mnt]$ ./ram_test_arm buff addr[0-31] = 0xbe82ac08,0xbe82ac09...0xbe82ac26(30),0xbe82ac27(31) buff value[4,12,20,28]:0xc0,0xa8,0x0,0xb2 buff value[0-32]: 0x0 0x0 0x0 0x0 0xc0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xa8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xb2 0x0 0x0 0x0buff value[4,5,6,7]:0xb2,0x0,0x0,0x0 buff value[0-32]: 0x0 0x0 0x0 0x0 0xb2 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0buff value[4(192),0-32]: 0x0 0x0 0x0 0x0 0xc0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 buff value[5(168),0-32]: 0x0 0x0 0x0 0x0 0xa8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 buff value[6(1),0-32]: 0x0 0x0 0x0 0x0 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 buff value[7(178),0-32]: 0x0 0x0 0x0 0x0 0xb2 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 buff value[8(255),0-32]: 0x0 0x0 0x0 0x0 0xb2 0x0 0x0 0x0 0xff 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0addr by inet_addr = [addr]0xbe82ac04,[value]0xb200a8c0, 2986387648 addr by char address: 0xbe82ac04,0xbe82ac05,0xbe82ac06,0xbe82ac07 addr by char value: 0xc0,0xa8,0x0,0xb2addr by inet_network = [addr]0xbe82ac04,[value]0xc0a800b2, 3232235698 addr by char address: 0xbe82ac04,0xbe82ac05,0xbe82ac06,0xbe82ac07 addr by char value: 0xb2,0x0,0xa8,0xc0

問題解決

分析上面的測試結果可以得出問題產生的原因(PC平臺和開發板ARM平臺均為小端類型):

在開發板上,sscanf函數中的%d格式符導致對它相應的指針變量內存地址寫時按照整型(int)的4字節對齊進行,如果sscanf中的地址不是4字節對齊,如buff+5=0xbe82ac0d時,程序會取比該地址小的4字節對齊字節進行寫內存,就是上面的buff+4地址,從而導致內存覆蓋;而buff+8,buff+12,buff+20,buff+28地址剛好都是4字節對齊地址,所以沒有產生內存覆蓋現象。

進一步進行分析可以得出根本的原因在于編譯器的處理。在PC上使用gcc編譯器構建程序可以OK,但是使用交叉編譯器arm-none-linux-gnueabi-gcc構建的程序在開發板上卻不OK,不同編譯器對這樣的問題的內存處理是不同的,我們沒法做任何假設。

問題原因找到了,解決就簡單了,其實解決方法已經在測試代碼中展示了,利用unix的網絡地址轉換接口可以方便地實現字符串地址和二進制地址的轉換,同時還能輕松地判斷地址是否有效,而沒必要自己去實現判斷和字符串解析工作。最后的處理代碼如下面這樣:

struct in_addr ip_addr; ...... if((ret=inet_aton(content->res_network_params.ip,&ip_addr))!=0) {//inet_aton() returns nonzero if the address is valid, zero if notptmp = (unsigned char *)&ip_addr.s_addr;memcpy(buff+1,ptmp,4);// binary form in network?byte order } if((ret!=0)&&(ret=inet_aton(content->res_network_params.mask,&ip_addr))!=0) {ptmp = (unsigned char *)&ip_addr.s_addr;memcpy(buff+5,ptmp,4); } ......

這里之所以沒使用inet_addr接口,是因為它失敗時返回的INADDR_NONE (usually -1)值有可能也是有效地址255.255.255.255,即可能會誤判。注意inet_addr和inet_aton接口均是產生網絡字節序(大端)的二進制,即字符串的高字節放在起始地址,而inet_network接口是產生主機字節序的二進制,主機字節序取決于平臺。

思考與學習

從這個bug中,我認識到編譯器產生的任何一個警告都不應該輕易或者想當然的去忽略,如果可能則盡量消除警告,否則必須有足夠的驗證表明該警告不會在實際環境中產生問題才能忽略。

此外,這個案例中我再一次深刻地認識到嵌入式與PC軟件開發的區別。嵌入式軟件的開發必須時刻注意到平臺、編譯器和操作系統接口的不同可能導致的問題,開發好的軟件在PC平臺上驗證OK并不能表示在目標平臺也一定OK,完整可靠的測試必須基于和生成部署環境真是一致的環境下進行,其結果才可靠和有說服力。

另一種解決方法

既然是由sscanf函數中使用不匹配的格式符引起的問題,那是不是可以通過使用匹配的格式符解決呢?答案是肯定的,只是我之前一致想當然的認為不存在對應與unsigned char或者char變量的輸入格式符。通過man sscanf查看手冊有如下兩個格式修飾符可用:


ConversionsThe following type modifier characters can appear in a conversion specification: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.


因此另一種更簡單的解決方法就是將一開始代碼中的%d格式符換成%hhu,這樣既不會有警告,程序在開發板上也運行正確,代碼改動量也小。而我之所以沒使用這種方法,是覺得有必要判斷一下字符串地址的有效性。

轉載于:https://my.oschina.net/shelllife/blog/172273

總結

以上是生活随笔為你收集整理的sscanf函数中类型不匹配警告引发的BUG和思考的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。