re: Socket Programming in C 常犯的錯誤
Posted on April 22nd, 2008 at 12:25 by fr3@K

昨天在網上閒逛, 在樂多上看到篇名為 “Socket Programming in C 常犯的錯誤” 的短文. 這短文作者的動機很好, 指出常見到的錯誤並提供個人看法與可能較好的作法. 這樣的行為很值得鼓勵. 但極可惜的是, 結果卻是很不理想的. 容我說句重話, 如果是考試的話該文是得不到我的分數的.

本想直接在該文回應作者, 但考慮到文中的兩個問題其實是很多不夠老練的 programmer 容易犯的毛病, 因此決定把我的評論寫在這裡, 自己留個紀錄, 也希望能供更多人參考.

原文照貼:

在一個簡單的網路程式,似乎有滿多人都會這樣寫:

char tmp[32];
memset( tmp, ‘\0′, sizeof(tmp));
int ret = recv( sock, tmp, sizeof(tmp), 0 );

把 buffer 填滿固然是一件好事,但是字串填滿之後,結果沒有\0了
就會發生意想不到的事情,可能會出現程式記憶體區段錯誤的問題
( 字元陣列的結尾正常結束都要有個 \0 才是正確的 )

假設我們不看 ret 回傳收到多大的資料,考慮使用 strcat 來把回傳的 buffer
串到更大的 buffer 字元陣列,應該要扣掉1來使用,避免發生記憶體區段錯誤!

char tmp[32];
memset( tmp, ‘\0′, sizeof(tmp));
int ret = recv( sock, tmp, sizeof(tmp) - 1, 0 );

首先是作者在文中指出需要在 buffer 最後保留一個 0x00, 以利後續字串操作. 這原則上是對的, 只是文中舉的例子是 recv, 以 recv/send/read/write 等 API 作 I/O 的時候讀寫的內容是 binary data 而不是 null-terminated string.1 以 socket I/O 來說, 除非 protocol 是如 HTTP 等的文本協議, 否則讀上來的有效 data 很可能中間就有值為 0x00 的 byte.2 而將最後有個 0x00 的 binary data 拿來當作 null-terminated string 操作, 後果可是很難設想的. 雖不至於造成記憶體錯誤, 功能上卻不是正確的. 又由於這樣的 bug 不總是容易重現, 造成的傷害反而可能更大. 所有讀上來的 data 應當要搭配上 recv 的非錯誤 return value (也就是讀出的 byte 數) 才能加以操作, 而不能將它視為字串, 拿來餵給 strlen/strcat 等 API .

另外作者說到, 在使用前先以 0x00 填滿 (fill) 整個 buffer 是好事. 但其實這是沒有任何意義的. 若 recv 失敗, buffer 指向的 memory 根本不會被拿來參考. 要是 recv 成功了, buffer 會被參考的部份則已經被覆蓋掉了. 如果 protocol 是文本協議, 只需如作者所說的在 recv 時先保留一個 byte, 並在成功時在最後補上個 null 就足夠了:

char tmp[32];
int ret = recv( sock, tmp, sizeof(tmp) - 1, 0 );
//if(ret != -1)
//{
//  tmp[ret] = 0;
//}
if(ret > 0)
{
  tmp[ret] = 0;
  // tmp is ready to be manipulated as string
}
else
{
  // handle error/EOF here
}
  1. 當然, binary data 本身也可以是 null-terminated string []
  2. 印象中, 即便是 HTTP, message 的 content 中間也可能出現值為 0x00 的 byte []
del.icio.us:re: Socket Programming in C 常犯的錯誤 digg:re: Socket Programming in C 常犯的錯誤 spurl:re: Socket Programming in C 常犯的錯誤 newsvine:re: Socket Programming in C 常犯的錯誤 furl:re: Socket Programming in C 常犯的錯誤 Y!:re: Socket Programming in C 常犯的錯誤 黑米共享書籤:re: Socket Programming in C 常犯的錯誤 推推王:re: Socket Programming in C 常犯的錯誤
Previous Post
« Video made for Microsoft’s sales team «
Next Post
» Strong Guarantee using Transaction »

5 Comments »

Comment #4369

基本上一般來講只有 return value > 0 才有必要被觀看
然後依照 ret 的數字大小直接對 buffer[ret] = 0×0; 就夠了

會寫這篇文章的主因是針對一般簡單的範例會把收到的資料透過 printf 出來
而在這個時候 %s 如果沒有抓到 就會引起很大的錯誤

for 初學者而言, 我覺得這個細節上探討是沒有必要的 :)

Comment by lilohuang — April 22, 2008 @ 17:56


Comment #4410

這的確是蠻嚴重的觀念偏差…
可能會誤導很多人,就像我現在maintain的專案裡,
都還是會看到很多無謂的memset,看到心理都不是很舒服…
程式經驗不足的人,反而會完全認同這種初始化動作是比較安全的說法…
你要好好的回應那個作者啊~~

Comment by person — May 9, 2008 @ 14:38


Comment #4411

Hi person,

看了你的回應, 自己又讀了一遍這篇的文字.

關於這個主題, 我以為我想說的都已經在文中清楚說明了, 暫時沒有想到要補充的地方. 雖然不確定我理解的對不對, lilohuang 的留言似乎表示他是初學者且沒興趣探討細節. 因此我沒打算回應, 抱歉.

若你對這主題有其他想法或是有想討論的地方, 還請不吝賜教.

Comment by fr3@K — May 9, 2008 @ 17:53


Comment #4432

To fr3@K

無知的你可能不曉得莉洛他的程式功力到什麼境界,
去多逛逛台灣最大的PTT BBS吧!少在這邊發這種廢文!!
莉洛大這篇文章是直接拷貝大陸的範例來解說的,
那篇原文本來就是用 memset,你真的很好笑ㄟ

Comment by ik — May 18, 2008 @ 22:24


Comment #4433

Hi ik,

請原諒我的孤陋寡聞, 小弟我的確不知道莉洛是那位或是功力有多”牛”. 我在這篇文字所表達的看法與莉洛本人或他的其他豐功偉業無關.

你給我的意見我不是理解的很清楚, 是說只要是莉洛說的話就是真理而不可有不一樣的看法嗎? 如果是這樣的話還請恕小弟不能同意. 抱歉.

希望我們在這裡的交流能夠將焦點放在技術層面, 感謝.

ps. 這個回應挖苦的目標不是莉洛, 而是另有其人. 不禮貌的地方還請莉洛包涵.

Comment by fr3@K — May 19, 2008 @ 0:36


Comments RSS TrackBack URI

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>