2010-02-02 02:28:22
来自: Milo
0 bug的评论



2
之前只看了3章便寫書評,對這書有點不公平。所以最後決心完成此書,並寫一份比較詳盡(或過於詳盡)的書評。我會先寫4-12章的,有時間再補充1-3章,最後再作總結。有別於之前只寫一些「懷疑錯誤」,我會分享一些「見仁見智」的意見,給大家交流討論,希望對其他讀者也有幫助。
我在前面的書評談到本書作者,也有猜測作者對某些知識的錯誤理解,我覺得不太對。現在只談書的內容,不作猜測。
或許這篇評論主要環繞書中發現的問題及改善意見,對整本書的優點和讀後感將會留在總結。請大家不要認為我只是單純發表負面的評價。
== 第4章: 設計自己的工程庫 ==
從這章開始,進入一些實際程式庫的開發,理論性的描述相對比前面三章少。這章比較短,主要以win32/pthread和BSD Socket/Winsock為例子去編寫跨平台代碼。
=== 程序bug/問題 ===
P.178 在 Linux_Win_Exit()裡,檢查到Winsock版本錯誤,才調用WSACleanup();反而版本正確時不調用WSACleanup()。該條件應該是之前WSAStartup() 是否成功。見 http://msdn.microsof t.com/en-us/library/ ms741549(VS.85).aspx
P.179 (不算是bug) #include <studio.h> 和 #include <unistd.h> 各重覆了一次。
=== 其他意見 ===
P.169 「Windows系統下遊戲開發很難利用GUI這類通用庫來完成,微軟被迫單獨開發了DirectX來實現高速遊戲開發。」
個人認為 DirectX 的功能主要是提供一個硬件/驅動軟件的抽像層。而不是本身GUI庫太「通用」的問題。可以說 GDI/GDI+和DirectX是完全不同的庫,而且除了2D/3D圖形,DirectX還包含聲音、輸入設備等功能。
P.170「以筆者經驗,STL 等通用庫的設計通常會以PC機作為目標運行平台,因此,其算法設計對於內存的邊界考慮較少,優化方向也大多采用"空間換時間"的原則,這在某些64MB RAM的嵌入式平台上,會成為很沉動的負擔,甚至不可使用。」
個人不太同意這個說法,實際上STL的設計及很多implementation,都考慮了很多執行及空間效能問題。當然要正確有效地使用STL也是一門學問,所以有《Effective STL》和《STL 源码剖析》等書籍。要先了解它們才能編寫比它更適合某應用的庫,例如EA提出的STL在遊戲編程上的問題,及他們內部做的改進http://www.open-std. org/jtc1/sc22/wg21/d ocs/papers/2007/n227 1.html
P.171 「操作系統已經為我們提供內存管理功能,我們可以在運行期自由地進行內存的動態申請和釋放、對像的創建和摧毀。」
這段有點不精確,操作系統和程序中間少了C Run-Time (CRT) Library。
P.171 「操作系統不會主動幫助C和C++程序員檢查內存錯誤。」
在Windows下,可以利用 _CrtDumpMemoryLeak() 等一系列CRT 函數幫助找出內存錯誤。
=== 本章感想 ===
本章示範以宏作跨平台抽像化,例如:
#ifdef WIN32
#define Linux_Win_CloseSocket closesocket
個人認為用 (inline) function 或類去實現會比較好,主要原因是有時候可能要多於一行statement (多行的宏很難debug),並且可以容易加入一些 defensive programming (這點留待總結才討論)。
但跨平台的很多細節並沒有談及,例如 endianess、data alignment、data type 等,建議可以加強。書中的例子都不是 64-bit 兼容的,這點必須要注意。
== 第5章: debug 工具 ==
這章指的debug工具其實是logging和一個統計模組。
=== 程序bug/問題 ===
P.184 pTM = asctime(pTM);
這個asctime()函式應該不是 thread-safe的。從它的declaration可以估計它每次都是返回同一個內部buffer。
P. 188 在 函式 dbg_bin_ascii(char* pPrintBuffer, char* pBuffer, int nLength) 裡
nCount += SafePrintf(pPrintBuffer+nCount, 256, "%c", *(pBuffer + i));
這裡的nCount 是目前已寫入的字元數,所以 256 應改為 256 - nCount,並要防止之溢出。P.189 有 5 個相同錯誤。
另外,256 是一個 literal,建議改用變量、宏或 const。
P.193 「C++的類編譯後效率會略低於C」
書中多處描述這種誤解。留待總結才討論。
P. 197 SCountSum() 是用來統計平均值的,但計算結果並非「平均值」。因為原文的可讀性(例如 Sum 是平均值......),以下用 pseudo code 表示:
ComputeMean(X)
if Mean = 0 then
Mean := X
else
Mean := (Mean + X) / 2
return Mean
每次加入元素便把它和平均值平均,所以這個「平均值」會嚴重傾向於之後的加入的元素,所以我認為這是一個錯誤。
最簡單的做法是分別儲存總和及元素個數:
ComputeMean(X)
Sum := Sum + X // 2010-02-02 09:33:35 令狐虫 提出更正
Count := Count + 1
return Sum / Count
但 Sum 會有機會 overflow,所以必須用長度/精度比較高的型別。
另一個方法是:
ComputeMean(X)
Mean := (Mean * Count + X) / (Count + 1)
Count := Count + 1
return Mean
這一樣會做成 overflow,但不用儲存一個精度比較長的型別,只要在乘的時候用較長的型別。而這式子也可以改為:
Mean := Mean * (Count / (Count + 1)) + X / (Count + 1)
這樣必需要浮點數,減少 overflow 的機會,但該除數卻有機會造成 underflow。
在我個人接觸的應用上通常只需要計算移動平均,方法是用一個 circular queue 的數組,當數組滿了就減去最前一個元素佔平均值的部份。這應該使比較準確且有用的效能統計方式。
P. 199 這個我真的看不明白:
inline int _Get0(void)
{
int nRet = rand();
return nRet^nRet;
}
為甚麼要這麼做才能獲得一個零呢? 如果必須調用 rand(),也在之後直接 return 0; 就可以吧?
P. 199 GetRandomBetween(nBegin, nEnd) 是用來「獲得給定區間內的隨機數」。但這個19行的函數有許多不正常的前設,例如「if (0 > nBegin) nBegin=-nBegin;」、「if (nBegin == nEnd) nEnd == nBegin + 10; // 強行定義範圍區間為 10」等,所以我認為是錯誤的。
我建議: (2010-02-06 13:41:25 魏理布赫 修正, 2010-02-07 10:24:34 hiessu 修正)
inline int GetRandomBetween(int nBegin, int nEnd)
{
return nBegin + (int)((long long)rand() * (nEnd - nBegin) / (RAND_MAX + 1));
}
這個函式傳回 [nBegin, nEnd) 半開區間 (不包括 nEnd)中的隨機數。nBegin 和 nEnd 為正負數都可以。
P. 203
class CTonyLowDebug
{
// ...
public:
// 至于 public,是因為可能某些內部模塊需要看一下這個指針,同時輸出
_APP_INFO_OUT_CALLBACK m_pInfoOutCallback;
// ...
};
這個就是本人在前一個評論中指出使用 friend keyword 適合的時機。為了給內部模塊使用,可以 friend class 或 friend function 去給予內部模塊存取權。利用public破壞了本書一再強調的封裝原則,所以我認為這是一個錯誤。
=== 文中錯誤 ===
P. 199 「其實C語言裡已經針對隨機數提供了大量的函數,......」
我只知道 rand() 和 srand(),還有嗎?
=== 其他意見 ===
P.186 這裡用了個我未見過的手法:
#define CON_DEBUG 1
#ifdef CON_DEBUG
#define CON_PRINTF printf
#else
#define CON_PRINTF /\
/printf
#endif
其目的是可以靠宏去關掉輸出。但更簡單的語法是:
#define CON_DEBUG 1
#ifdef CON_DEBUG
#define CON_PRINTF(...) printf(__VA_ARGS__)
#else
#define CON_PRINTF(...)
#endif
P. 209 CTonyLowDebug::Debug2File() 是用來寫一個訊息到 log 檔案的。但這個函式每次都 fopen() -> fprintf() -> fclose() 去寫訊息,系統負擔會很高。建議該物件只 fopen() 一次,之後每次寫訊息直接 fprintf()。如果擔心緩衝沒有寫到儲存設備裡,可以在 fprintf() 調用 fflush()。
=== 本章感想 ===
我認為本章所提及的功能都是許多應用程序所需的,每個C/C++的應用程式都應該編寫或使用現成的庫。不過可能並不應該歸類為 Debug。一些應該歸類為Profiling。而 Profiling 也可以實現多種形式,例如 real-time/offline analysis 的、hierarhical/set 的、時間/次數等等。
多個函數的效能也可以進一步提升,例如 SafePrintf(buffer, size, "%c", *(buffer2 + i)) 這種調用就是太浪費,一個字元的賦值和邊界檢測就可以了。同樣,"%02X" 也可用簡單的函式代替。
== 第6章: 鎖 ==
這一章主要描述一些綫程同步機制的 utility 類,及一些書中稱為「二元動作」的編程規則(詳見其他意見)。
=== 程序bug/問題 ===
P. 231 是利用一個模版去保裝一個物件,使該物件 thread-safe。用模版就不應該用 char* 去儲存一個模版型別的物件:
template <class MVAR_TYPE>
class MVAR
{
private:
char* m_pBegin;
// ...
public:
MVAR() { m_Begin=new char[sizeof(MVAR_TYPE)];
// ...
};
這個模版沒有調用 MVAR_TYPE 物件的 constructor, destructor, copy constructor, assignment operator,而只用 memcpy()。當 MVAR_TYPE 物件有 non-trivial 的這類函式,就會做成一大堆 bug。
=== 其他意見 ===
P.213-P.225 主要在討論「二元動作理論」,其實就是指,一些動作需要在一個 scope 之內執行兩個動作,例如 mutex 的 lock/unlock,scope 內的 new/delete 等。書中用了很大編幅的制定一些規定和原則,例如:
void CFoo::Func(void)
{
m_Lock.Lock();
{
// ...
}
m_Lock.Unlock();
}
但是,其實在 C++ 中有很簡單的(標準)解決方法,避免忘記第二個動作,方法如下:
class CScopeLock
{
public:
CScopeLock(CLock& lock) : m_Lock(lock)
{
m_Lock.Lock();
}
~CScopeLock() throw()
{
m_Lock.Unlock();
}
private:
CLock& m_Lock;
};
void CFoo::Func(void)
{
ScopeLock t(m_Lock);
// ...
}
這個方式除了簡單、自動之外,亦解決了 break/goto/return/throw 做成跳出 scope 時的問題。C++ 的 stack unwinding 機制會自動調用 scope 內的 local variable destructor。這手法亦是編寫 exception-safe 代碼常用的手法。本章寫的 goto 手法也不需要,因為其實C++編譯器已經自動加入這些"goto"的編碼。這個手法叫做 RAII http://en.wikipedia. org/wiki/Resource_Ac quisition_Is_Initial ization
new/delete 可以利用 std::auto_ptr<T>做到同樣的效果:
void Func(void)
{
std::auto_ptr<CFoo> foo(new CFoo);
// ...
}
不過注意 auto_ptr<T> 在 destructor 是調 operator delete (而不是 operator[] delete 或 free() ),所以不可以傳入用 new[]或malloc() 的指針。有需要也可以寫個簡單的 CScopeXXX 類。
P. 230 「多綫程安全的變量模板」,使用 critical section/mutex 來保護一個 int/char/等變量。建議使用 automic integer,例如 Windows 的 InterlockedIncrement 一系列 API,效能會高很多。
P. 241 「單寫多讀鎖」我並無詳細看它是否會有問題。如陳碩所言,要証明這樣的多線程代碼是正確性是困難的。對於本書的多線程編程思路,例如同步粒度、調用sleep() 等等,將會在總結討論。
待續......
0 bug的评论




2
之前只看了3章便寫書評,對這書有點不公平。所以最後決心完成此書,並寫一份比較詳盡(或過於詳盡)的書評。我會先寫4-12章的,有時間再補充1-3章,最後再作總結。有別於之前只寫一些「懷疑錯誤」,我會分享一些「見仁見智」的意見,給大家交流討論,希望對其他讀者也有幫助。
我在前面的書評談到本書作者,也有猜測作者對某些知識的錯誤理解,我覺得不太對。現在只談書的內容,不作猜測。
或許這篇評論主要環繞書中發現的問題及改善意見,對整本書的優點和讀後感將會留在總結。請大家不要認為我只是單純發表負面的評價。
== 第4章: 設計自己的工程庫 ==
從這章開始,進入一些實際程式庫的開發,理論性的描述相對比前面三章少。這章比較短,主要以win32/pthread和BSD Socket/Winsock為例子去編寫跨平台代碼。
=== 程序bug/問題 ===
P.178 在 Linux_Win_Exit()裡,檢查到Winsock版本錯誤,才調用WSACleanup();反而版本正確時不調用WSACleanup()。該條件應該是之前WSAStartup() 是否成功。見 http://msdn.microsof
P.179 (不算是bug) #include <studio.h> 和 #include <unistd.h> 各重覆了一次。
=== 其他意見 ===
P.169 「Windows系統下遊戲開發很難利用GUI這類通用庫來完成,微軟被迫單獨開發了DirectX來實現高速遊戲開發。」
個人認為 DirectX 的功能主要是提供一個硬件/驅動軟件的抽像層。而不是本身GUI庫太「通用」的問題。可以說 GDI/GDI+和DirectX是完全不同的庫,而且除了2D/3D圖形,DirectX還包含聲音、輸入設備等功能。
P.170「以筆者經驗,STL 等通用庫的設計通常會以PC機作為目標運行平台,因此,其算法設計對於內存的邊界考慮較少,優化方向也大多采用"空間換時間"的原則,這在某些64MB RAM的嵌入式平台上,會成為很沉動的負擔,甚至不可使用。」
個人不太同意這個說法,實際上STL的設計及很多implementation,都考慮了很多執行及空間效能問題。當然要正確有效地使用STL也是一門學問,所以有《Effective STL》和《STL 源码剖析》等書籍。要先了解它們才能編寫比它更適合某應用的庫,例如EA提出的STL在遊戲編程上的問題,及他們內部做的改進http://www.open-std.
P.171 「操作系統已經為我們提供內存管理功能,我們可以在運行期自由地進行內存的動態申請和釋放、對像的創建和摧毀。」
這段有點不精確,操作系統和程序中間少了C Run-Time (CRT) Library。
P.171 「操作系統不會主動幫助C和C++程序員檢查內存錯誤。」
在Windows下,可以利用 _CrtDumpMemoryLeak() 等一系列CRT 函數幫助找出內存錯誤。
=== 本章感想 ===
本章示範以宏作跨平台抽像化,例如:
#ifdef WIN32
#define Linux_Win_CloseSocket closesocket
個人認為用 (inline) function 或類去實現會比較好,主要原因是有時候可能要多於一行statement (多行的宏很難debug),並且可以容易加入一些 defensive programming (這點留待總結才討論)。
但跨平台的很多細節並沒有談及,例如 endianess、data alignment、data type 等,建議可以加強。書中的例子都不是 64-bit 兼容的,這點必須要注意。
== 第5章: debug 工具 ==
這章指的debug工具其實是logging和一個統計模組。
=== 程序bug/問題 ===
P.184 pTM = asctime(pTM);
這個asctime()函式應該不是 thread-safe的。從它的declaration可以估計它每次都是返回同一個內部buffer。
P. 188 在 函式 dbg_bin_ascii(char* pPrintBuffer, char* pBuffer, int nLength) 裡
nCount += SafePrintf(pPrintBuffer+nCount, 256, "%c", *(pBuffer + i));
這裡的nCount 是目前已寫入的字元數,所以 256 應改為 256 - nCount,並要防止之溢出。P.189 有 5 個相同錯誤。
另外,256 是一個 literal,建議改用變量、宏或 const。
P.193 「C++的類編譯後效率會略低於C」
書中多處描述這種誤解。留待總結才討論。
P. 197 SCountSum() 是用來統計平均值的,但計算結果並非「平均值」。因為原文的可讀性(例如 Sum 是平均值......),以下用 pseudo code 表示:
ComputeMean(X)
if Mean = 0 then
Mean := X
else
Mean := (Mean + X) / 2
return Mean
每次加入元素便把它和平均值平均,所以這個「平均值」會嚴重傾向於之後的加入的元素,所以我認為這是一個錯誤。
最簡單的做法是分別儲存總和及元素個數:
ComputeMean(X)
Sum := Sum + X // 2010-02-02 09:33:35 令狐虫 提出更正
Count := Count + 1
return Sum / Count
但 Sum 會有機會 overflow,所以必須用長度/精度比較高的型別。
另一個方法是:
ComputeMean(X)
Mean := (Mean * Count + X) / (Count + 1)
Count := Count + 1
return Mean
這一樣會做成 overflow,但不用儲存一個精度比較長的型別,只要在乘的時候用較長的型別。而這式子也可以改為:
Mean := Mean * (Count / (Count + 1)) + X / (Count + 1)
這樣必需要浮點數,減少 overflow 的機會,但該除數卻有機會造成 underflow。
在我個人接觸的應用上通常只需要計算移動平均,方法是用一個 circular queue 的數組,當數組滿了就減去最前一個元素佔平均值的部份。這應該使比較準確且有用的效能統計方式。
P. 199 這個我真的看不明白:
inline int _Get0(void)
{
int nRet = rand();
return nRet^nRet;
}
為甚麼要這麼做才能獲得一個零呢? 如果必須調用 rand(),也在之後直接 return 0; 就可以吧?
P. 199 GetRandomBetween(nBegin, nEnd) 是用來「獲得給定區間內的隨機數」。但這個19行的函數有許多不正常的前設,例如「if (0 > nBegin) nBegin=-nBegin;」、「if (nBegin == nEnd) nEnd == nBegin + 10; // 強行定義範圍區間為 10」等,所以我認為是錯誤的。
我建議: (2010-02-06 13:41:25 魏理布赫 修正, 2010-02-07 10:24:34 hiessu 修正)
inline int GetRandomBetween(int nBegin, int nEnd)
{
return nBegin + (int)((long long)rand() * (nEnd - nBegin) / (RAND_MAX + 1));
}
這個函式傳回 [nBegin, nEnd) 半開區間 (不包括 nEnd)中的隨機數。nBegin 和 nEnd 為正負數都可以。
P. 203
class CTonyLowDebug
{
// ...
public:
// 至于 public,是因為可能某些內部模塊需要看一下這個指針,同時輸出
_APP_INFO_OUT_CALLBACK m_pInfoOutCallback;
// ...
};
這個就是本人在前一個評論中指出使用 friend keyword 適合的時機。為了給內部模塊使用,可以 friend class 或 friend function 去給予內部模塊存取權。利用public破壞了本書一再強調的封裝原則,所以我認為這是一個錯誤。
=== 文中錯誤 ===
P. 199 「其實C語言裡已經針對隨機數提供了大量的函數,......」
我只知道 rand() 和 srand(),還有嗎?
=== 其他意見 ===
P.186 這裡用了個我未見過的手法:
#define CON_DEBUG 1
#ifdef CON_DEBUG
#define CON_PRINTF printf
#else
#define CON_PRINTF /\
/printf
#endif
其目的是可以靠宏去關掉輸出。但更簡單的語法是:
#define CON_DEBUG 1
#ifdef CON_DEBUG
#define CON_PRINTF(...) printf(__VA_ARGS__)
#else
#define CON_PRINTF(...)
#endif
P. 209 CTonyLowDebug::Debug2File() 是用來寫一個訊息到 log 檔案的。但這個函式每次都 fopen() -> fprintf() -> fclose() 去寫訊息,系統負擔會很高。建議該物件只 fopen() 一次,之後每次寫訊息直接 fprintf()。如果擔心緩衝沒有寫到儲存設備裡,可以在 fprintf() 調用 fflush()。
=== 本章感想 ===
我認為本章所提及的功能都是許多應用程序所需的,每個C/C++的應用程式都應該編寫或使用現成的庫。不過可能並不應該歸類為 Debug。一些應該歸類為Profiling。而 Profiling 也可以實現多種形式,例如 real-time/offline analysis 的、hierarhical/set 的、時間/次數等等。
多個函數的效能也可以進一步提升,例如 SafePrintf(buffer, size, "%c", *(buffer2 + i)) 這種調用就是太浪費,一個字元的賦值和邊界檢測就可以了。同樣,"%02X" 也可用簡單的函式代替。
== 第6章: 鎖 ==
這一章主要描述一些綫程同步機制的 utility 類,及一些書中稱為「二元動作」的編程規則(詳見其他意見)。
=== 程序bug/問題 ===
P. 231 是利用一個模版去保裝一個物件,使該物件 thread-safe。用模版就不應該用 char* 去儲存一個模版型別的物件:
template <class MVAR_TYPE>
class MVAR
{
private:
char* m_pBegin;
// ...
public:
MVAR() { m_Begin=new char[sizeof(MVAR_TYPE)];
// ...
};
這個模版沒有調用 MVAR_TYPE 物件的 constructor, destructor, copy constructor, assignment operator,而只用 memcpy()。當 MVAR_TYPE 物件有 non-trivial 的這類函式,就會做成一大堆 bug。
=== 其他意見 ===
P.213-P.225 主要在討論「二元動作理論」,其實就是指,一些動作需要在一個 scope 之內執行兩個動作,例如 mutex 的 lock/unlock,scope 內的 new/delete 等。書中用了很大編幅的制定一些規定和原則,例如:
void CFoo::Func(void)
{
m_Lock.Lock();
{
// ...
}
m_Lock.Unlock();
}
但是,其實在 C++ 中有很簡單的(標準)解決方法,避免忘記第二個動作,方法如下:
class CScopeLock
{
public:
CScopeLock(CLock& lock) : m_Lock(lock)
{
m_Lock.Lock();
}
~CScopeLock() throw()
{
m_Lock.Unlock();
}
private:
CLock& m_Lock;
};
void CFoo::Func(void)
{
ScopeLock t(m_Lock);
// ...
}
這個方式除了簡單、自動之外,亦解決了 break/goto/return/throw 做成跳出 scope 時的問題。C++ 的 stack unwinding 機制會自動調用 scope 內的 local variable destructor。這手法亦是編寫 exception-safe 代碼常用的手法。本章寫的 goto 手法也不需要,因為其實C++編譯器已經自動加入這些"goto"的編碼。這個手法叫做 RAII http://en.wikipedia.
new/delete 可以利用 std::auto_ptr<T>做到同樣的效果:
void Func(void)
{
std::auto_ptr<CFoo> foo(new CFoo);
// ...
}
不過注意 auto_ptr<T> 在 destructor 是調 operator delete (而不是 operator[] delete 或 free() ),所以不可以傳入用 new[]或malloc() 的指針。有需要也可以寫個簡單的 CScopeXXX 類。
P. 230 「多綫程安全的變量模板」,使用 critical section/mutex 來保護一個 int/char/等變量。建議使用 automic integer,例如 Windows 的 InterlockedIncrement 一系列 API,效能會高很多。
P. 241 「單寫多讀鎖」我並無詳細看它是否會有問題。如陳碩所言,要証明這樣的多線程代碼是正確性是困難的。對於本書的多線程編程思路,例如同步粒度、調用sleep() 等等,將會在總結討論。
待續......
本评论版权属于作者Milo,并受法律保护。除非评论正文中另有声明,没有作者本人的书面许可任何人不得转载或使用整体或任何部分的内容。


2010-02-02 02:42:01 Season
呵呵 先占楼等大神们进一步讨论
2010-02-02 03:34:09 ndv
对于真正的专业人士而言,Milo的前一评以及陈硕的书评其实已经暴露了本书的专业问题了,作者的素质问题暴露出来是个意外收获:)原本我觉得Milo其实不应该浪费时间在这本书上,因为对个人而言读书时间是宝贵的,但这本书实在不配。不过转念一想,认真地对待这件事或许也是应该的,因为现在的风气就是太缺认真!反而让某些人有机可乘,而容忍一时往往遗祸无穷!为了防止类似的搞笑事件,一个“认真”的结果固然重要,但“认真”的过程更为重要,谩骂或武断下结论只能图一时之快,真正需要的是专业的精神和据理力争,不仅仅因为这是可行的方法,还因为这是唯一根本的方法,我想Milo其实在做一件功德无量的事:)
2010-02-02 07:02:43 甲骨文
我没看过这本书,只是说点自己的看法1。豆瓣的评论功能不同于一般媒体的书评,
它既能作为评论供各种阶层的读者参考,
也能当成倾向性比较强的个人读书笔记。
我个人更倾向把Milo的评论当作一篇读书笔记来看。
2。专业问题
每个人都有自己的技术背景,
从10个人的角度看同一个事物可能会有10种看法。
谭浩强的c语言那本书专业性强不强?
我们把这本书当成科普书看行不行?
这本书的定位是面向“真正的专业人士”的吗?
3。百花起放 百家争鸣
放下愤怒虚荣傲慢各种偏见,
大家都来写书或者就事论事的进行评论吧。
2010-02-02 07:09:23 knight_stalker
小时候学编程,因为看的旁门左道中文书太多 bug 而走了不少弯路 ……蛋 bug 这名字太具忽悠性,一定得踩 ……
2010-02-02 07:26:20 knight_stalker
因为读者不够专业,所以书就可以不严谨,小错误就无所谓吗?一个小错误,专业人士一下就看出问题,无伤大雅,但照做的初学者死活跑不通,耽误了大量时间,可恨不可恨 ?
如果书籍出版是按照正规流程,让有专业知识的审稿人来回审几遍,也不至于这么多 bug ……
2010-02-02 08:49:18 lorking
2。专业问题每个人都有自己的技术背景,
从10个人的角度看同一个事物可能会有10种看法。
谭浩强的c语言那本书专业性强不强?
我们把这本书当成科普书看行不行?
这本书的定位是面向“真正的专业人士”的吗?
--------------------------
汗!很明显,作者的本意是地。
2010-02-02 08:58:19 当仁不让的老孙
汗颜。真不敢自称专业人士了。
2010-02-02 09:07:42 丁丁虫
2010-02-02 07:02:43 甲骨文每个人都有自己的技术背景,
从10个人的角度看同一个事物可能会有10种看法。
------------------------------------------
我只知道10个人读一部小说可能会有10种看法,
还真不知道10个人看一本技术书也会有10种看法~
2010-02-02 09:07:56 西北
学习中。。。2010-02-02 09:24:03 figure9
仔细看了下书评,学习中,赞LZ认真的态度。想知道LZ是如何对对C/C++的理解达到这种程度的,LZ可否推荐一些学习C++的好书以及一些学习方法,偶现在正在从C#向C/C++转化
2010-02-02 09:30:31 [已注销]
milo 写得很好,原作者基础不扎实的问题越发暴露出来了。补充一句,auto_ptr 在stl container里面复制对象的时候会出问题,建议使用boost::shared_ptr以及boost库里其他相关智能指针。2010-02-02 09:33:35 令狐虫
ComputeMean(X)Sum := X
Count := Count + 1
return Sum / Count
应该是
ComputeMean(X)
Sum := Sum + X
Count := Count + 1
return Sum / Count
?
2010-02-02 09:45:17 [已注销]
楼上发现的应该是笔误吧2010-02-02 10:27:45 Milo
2010-02-02 09:33:35 令狐虫ComputeMean(X)
Sum := X
Count := Count + 1
return Sum / Count
应该是
ComputeMean(X)
Sum := Sum + X
Count := Count + 1
return Sum / Count
?
樓上是對的. 我去更正.
2010-02-02 10:34:04 sprite
搬个板凳坐等老师出现我觉得Milo的评论简直可以写进教科书了
2010-02-02 10:43:15 sprite
2010-02-02 07:02:43 甲骨文我们把这本书当成科普书看行不行?
------------------------------------------
可能肖老师自己都不会答应这个荒唐的要求
2010-02-02 10:49:08 Milo
2010-02-02 07:02:43 甲骨文我没看过这本书,只是说点自己的看法
2。专业问题
每个人都有自己的技术背景,
从10个人的角度看同一个事物可能会有10种看法。
谭浩强的c语言那本书专业性强不强?
我们把这本书当成科普书看行不行?
这本书的定位是面向“真正的专业人士”的吗?
---
第1和3點都同意,我會在總結嘗試寫多點像書評的文字。第2點的話,可看看编辑推荐
http://product.dangd
那段「如果你是一名...」是在封底的。我相信本書的目標是面向專業對像。
2010-02-02 10:54:35 sprite
嗯,想了一下,可能有一点我没有说明,这里澄清一下。我这本书写出来,还是有目标客户群的。
我预设的目标读者,是大学本科三年级以上,到毕业后工作三年的人,差不多是20~25岁这个年龄段。
也就是说,本书不是入门教材,看本书的读者,起码应该具有以下条件:
1、至少具有高中毕业以上文化程度。
2、能阅读并理解现代汉语白话文。
3、能有简单的英文基础,起码,懂得查英汉词典。
4、C语言起码要能理解指针。
5、C++语言起码要会写几本的类,嗯,如果能有点模板知识就更好了。
具有以上基础,才能顺利阅读和理解本书的知识范畴。请各位准读者对号入座,看看是否符合自己的实际情况,免得买错了,又来后悔。
------------------------------------------
这个是肖老师在china-pub上对目标客户群提出的定义
2010-02-02 11:03:49 甲骨文
没有最专业只有更专业为了提高大家的专业水平
原书作者和Milo一起来编写一下本书第二版如何?
这可能是0bug门的一个win-win的解决办法
2010-02-02 11:09:32 大師
LZ好,我敬佩你。2010-02-02 11:12:02 贾里
> P.184 pTM = asctime(pTM);> 這個asctime()函式應該不是 thread-safe的。從它的declaration可以估計它每次都是返回同一個內部buffer。 P.184 pTM = asctime(pTM);
假如是在windows下,asctime()会有一个线程安全版本,但是POSIX规格说明asctime()的线程安全版本是asctime_r(),进行跨平台设计时应该考虑到这样的细节,这至少是作者的一个疏忽。
> P.186 這裡用了個我未見過的手法:
> #define CON_DEBUG 1
> #ifdef CON_DEBUG
> #define CON_PRINTF printf
> #else
> #define CON_PRINTF /\
> /printf
> #endif
>
> 其目的是可以靠宏去關掉輸出。但更簡單的語法是:
> #define CON_DEBUG 1
> #ifdef CON_DEBUG
> #define CON_PRINTF(...) printf(__VA_ARGS__)
> #else
> #define CON_PRINTF(...)
> #endif
关于__VA_ARGS__宏,windows平台下,vc++ 2005及其以后的版本才引入,我不太熟悉unix/linux平台,所以并不清楚前一个手法的宏定义在unix/linux平台下是否正确(应该是正确的),而后一个手法是否已经在unix/linux平台下实现了呢?假如前一个手法是正确的话,那么可以视为为了跨平台设计而使用的手段,并且考虑了向下兼容性的问题。
> P. 199 GetRandomBetween(nBegin, nEnd) 是用來「獲得給定區間內的隨機數」。但這個19行的函數有許多不正常的前設,例如「if (0 > nBegin) nBegin=-nBegin;」、「if (nBegin == nEnd) nEnd == nBegin + 10; // 強行定義範圍區間為 10」等,所以我認為是錯誤的。
>
> 我建議:
> inline int GetRandomBetween(int nBegin, int nEnd)
> {
> return nBegin + (int)((long)rand() * (nEnd - nBegin) / RAND_MAX);
> }
我下意识的写法是:
inline int GetRandomBetween( int nBegin, int nEnd )
{
return nBegin + (int)((long)rand()%(nEnd - nBegin);
}
后来意识到传进来的参数会有问题,必须进行保护,milo兄,受教了。
没看过作者的这本书,继续期待milo兄的评论,好好学习,呵呵。
2010-02-02 11:13:10 取啥名呢
Milo 先生:「見人見志」是特意这样写的还是“见仁见智”的误用?2010-02-02 11:15:53 Milo
2010-02-02 11:13:10 取啥名呢Milo 先生:「見人見志」是特意这样写的还是“见仁见智”的误用?
是我錯... 去更正.
2010-02-02 11:18:15 风中有只凌乱鸡
пϡƽѡЩRaiiû2010-02-02 11:21:48 lorking
甲骨文的要求可操作性并不强其一:milo有没有时间,和精力。大家公认的,写书是个累活。
其二:肖老师有没有这气量。真的不是想贬低肖老师,气量大和脸皮厚某种情况下其实是同义词。反正换作我我会羞愧的连douban都不会上的,更别说联合出书了。不能奢求每个人都有曹操那样的胸襟。
2010-02-02 11:22:50 Milo
2010-02-02 11:12:02 贾里這是 ISO C standard,gcc 應該很早就有了,那個版本開始我不清楚。
http://www.delorie.c
還有一點,書中的手法有可能導致編譯錯誤甚至邏輯錯誤,例如:
if (p == NULL)
CON_PRINTF("...");
Foo();
關了 CON_DEBUG,preprocess 後會變成
if (p == NULL)
//CON_PRINTF("...");
Foo();
原來 Foo() 就會放在 if 之下,可編譯但邏輯錯誤,這種錯誤可能看代碼很難發現。
另外,用 rand() % X 我個人是覺得不好的,如果 X 足夠大,接近 RAND_MAX,那麼隨機的分佈就不是 uniform 了 (假設 rand() 是 uniform distributed)。
2010-02-02 11:23:34 knight_stalker
写好书累,写烂书绝对不累。2010-02-02 11:34:22 贾里
@milo有一种惯用法可以解决这个问题:
#define CON_DEBUG 1
#ifdef CON_DEBUG
#define CON_PRINTF printf
#else
#define CON_PRINTF
do
{
//printf
}while(0);
#endif
至于RAND_MAX,刚看了定义:
/* Maximum value that can be returned by the rand function. */
#define RAND_MAX 0x7fff
汗颜。。。。。。
2010-02-02 11:40:37 Milo
@贾里你提出的方法應該不行, 因為用沒參數的宏,CON_PRINTF(X, Y, Z) 會把 (X, Y, Z) 放到 substitution 之後,就是 do ... while(0);(X, Y, Z)。
2010-02-02 11:56:39 贾里
@milo嗯,是我对宏的理解不足。
那其实还不如定义成
#define CON_DEBUG 1
#ifdef CON_DEBUG
#define CON_PRINTF printf
#else
#define CON_PRINTF
#endif
这样有( x, y, z );可以占据一行而不至于产生逻辑错误,只是如此是否会有问题?
2010-02-02 12:19:24 取啥名呢
认真地对待这件事或许也是应该的,因为现在的风气就是太缺认真!真正需要的是专业的精神和据理力争,不仅仅因为这是可行的方法,还因为这是唯一根本的方法,我想Milo其实在做一件功德无量的事:)
========================================
很欣赏!
感谢MILO先生认真的评论
受益非浅
写书评不是很容易的事情
有时候并不比写书容易
2010-02-02 12:35:18 取啥名呢
2010-02-02 07:02:43 甲骨文2。专业问题
每个人都有自己的技术背景,
从10个人的角度看同一个事物可能会有10种看法。
谭浩强的c语言那本书专业性强不强?
我们把这本书当成科普书看行不行?
这本书的定位是面向“真正的专业人士”的吗?
=======================
谭浩强的c语言那本书漏洞百出,误尽初学者,倒真的不敢恭维
您的意思本书的作者也有志于此?
科普书也不可以乱写
科普书就更不可以写错
初学者接受了错误的概念
可能要用几十倍的学习时间去更正而且不一定改得过来
2010-02-02 12:36:59 锲而不舍求妹汁
P.171 「操作系統已經為我們提供內存管理功能,我們可以在運行期自由地進行內存的動態申請和釋放、對像的創建和摧毀。」這段有點不精確,操作系統和程序中間少了C Run-Time (CRT) Library。
===========================================
Windows下有VirtualAlloc,VirtualFree,HeapAlloc等一堆API,作者说的也不算错。
P.171 「操作系統不會主動幫助C和C++程序員檢查內存錯誤。」
在Windows下,可以利用 _CrtDumpMemoryLeak() 等一系列CRT 函數幫助找出內存錯誤。
==============================================
既然是CRT函数,应该属于编译器范畴吧,跟操作系统的关系确实不大。但或许CRT为了实现这部分功能需要操作系统提供的支持,这点我不确认。
2010-02-02 12:52:31 贾里
@ 取啥名呢要考虑现实和理想的差距,现实是,这样的书写出来肯定不可能没有bug,肯定会遭骂,谁敢保证自己写出来的东西百分之百准确?我觉得对我来说,这本书应该是很有帮助的,可能有bug,但是在讨论中一次次迭代改进,这不是很好么? 有这样一本书,并且在讨论中得到进步,我觉得要比没有这本书要好得多。
作者拉不下脸来承认错误,我觉得他的态度确实有问题,但不能因人废言,我觉得不要因为其他无关因素而阻碍了自己的进步才是最重要的。
2010-02-02 12:52:54 Milo
@贾里這個是最簡單的, 不過又會有其他問題。例如 CON_DEBUG("%d", foo()), 而 foo 只在 debug configuration 定義,而且 foo() 是一定會被執行的。
2010-02-02 13:02:48 Milo
@missdeerP.171 是的,Win32 的 API 是可以調用的。但全本書都是用 CRT 的,所以我建議可以寫清楚一點。所以這個評論是放在「其他意見」而不是「文中錯誤」。
至於下面的,我也是認為最好描述得清楚一點。操作系統五花百門,是否「不能幫助C/C++ 程序員檢查內存錯誤」是值得相確的。或許可以寫,因為跨平台關係,程序員可以忽略操作系統或CRT等提供相應的供能。或是說 C/C++ 標準裡並不支持這些功能等。
2010-02-02 13:20:26 贾里
@Milo谢谢指点,继续学习~
2010-02-02 13:27:07 取啥名呢
@ 贾里要考虑现实和理想的差距,现实是,这样的书写出来肯定不可能没有bug,肯定会遭骂,谁敢保证自己写出来的东西百分之百准确?我觉得对我来说,这本书应该是很有帮助的,可能有bug,但是在讨论中一次次迭代改进,这不是很好么?有这样一本书,并且在讨论中得到进步,我觉得要比没有这本书要好得多。
作者拉不下脸来承认错误,我觉得他的态度确实有问题,但不能因人废言,我觉得不要因为其他无关因素而阻碍了自己的进步才是最重要的。
======================
您说的我都同意
所以我觉得有可能我前面有些话表达的有些词不达意吧
我想补充的一点是
除了作者对待批评的态度
我也反对业界对待不严谨的麻木不仁
2010-02-02 13:36:46 King Bear
很认真很仔细,学习中,顶2010-02-02 13:37:31 贾里
@取啥名呢摊手。。这又涉及到权衡质量和成本的问题了,都是追求软件能跑就好的质量的话,也难怪会出现这样的情况。
2010-02-02 14:10:06 取啥名呢
@ 贾里所以,……又想起了MADE IN CHINA
2010-02-02 22:32:01 [已注销]
基础很重要,抽象能力也很重要,但更重要的是态度。支持Milo,留记号看回复。
2010-02-03 12:45:39 树梢~光速
我就在说,怎么有人提谭浩强的那本书,难道0 bug差到需要用谭来侮辱肖老师了?
2010-02-03 18:54:15 iLRainyday
至少从人品上,作者和谭老师是没法比的。技术差,也许只是一时;人品差,基本上就是一辈子了。2010-02-04 20:38:06 肖舸
我的新书《0 bug -- C/C++商用工程之道》近期已经面市,经过出版社统计,上市一个月20天左右,销售共计2500多册(出版社数据),这在专业性技术书籍中,应该还是比较乐观的。我作为作者本人,也衷心感谢各位读者的鼓励和支持,我将一如既往地努力进行后续版本的修订和写作,为大家提供更好的精品书籍。应该说,我写这本书,还是有一定目的的。
目前的社会上,讲C和C++语言的书籍汗牛充栋,但是,我发现有个问题都没有讲,就是“并行计算”。
很多学习计算机软件编程的朋友,在学校中学习到了很多很好的知识,但是,就笔者所知(可能是笔者孤陋寡闻),确实很少有大学开设《并行计算》这门课程。
但我们知道,现实社会中,目前32位多任务操作系统,如Windows、Linux、Unix等操作系统已经完全普及,哪怕连很小的嵌入式设备,如手机上,都开始使用多任务开发环境,这就要求现代程序员必须具有并行程序设计能力,但无疑,目前大多数程序员缺乏这种能力。最直观的例子就是Intel等CPU厂商,为了自己的多核CPU好销,已经开始在各地区开设程序员进修班,以各种形式向大家培训并行程序开发技能,以便解决整个业界无法适应多核多任务开发环境的需求这个问题。
我也是在这种情况下考虑写作本书的,本书虽然定位于0bug,无错化开发,宣导商业化的务实开发思维,但这仅仅是一个方面,传统单任务程序设计的无错化方法,其实已经有很多参考资料了,笔者认为再“炒剩饭”没有多大意义。笔者从第一天开始写作本书,就致力于解决大家关心的一个核心问题:“如何书写无错化的并行程序?”,这应该说是本书的核心宗旨。
这么做的意义显而易见,由于现在是互联网的社会,网络化开发采用C/S模型,但是,大量的书籍讲了Client端的开发和设计,缺很少,甚至没有书籍来描述服务器端的设计技能。但偏偏目前业界几乎所有的应用,都已经逐渐网络化,在未来的集中式和分布式运行模型下,大量的设计需求要求程序员具有多任务服务器的设计能力,这是一个现实的情况。
现在,哪怕一个很小的嵌入式家用路由器,其实都要求具有7*24小时的连续服务能力。这很好理解,大家想想自己家里的小路由器,买回来连通后,是不是很少断电和关机?
所以我作为作者,才认为这本书有这么大的现实意义,我是商用程序员,做事情讲究务实,我写书,也希望切切实实帮助现在的大多数程序员解决自己身边,现在就遇到的问题,因此我从这个角度切入,写作本书。据某些读者朋友反映,本书是:“目前为止我知道的惟一一本关注服务器端程序设计的 C++ 书。而且又是国人的原创作品,十分难能可贵。”
不过呢,俗话说,林子大了,什么鸟都有,网络作为现实社会的一个剪影,确确实实什么人都有的。本书出来后,一直受到很多不必要的干扰,这个,我在博文《关于《0 bug-C/C++商用工程之道》一书出版前后的故事》中,已经有了详细论述,此处就不再详述。
但是,我作为作者,也考虑了,不能把提批评意见的朋友都一竿子打死,这也不是客观的科学态度。毕竟,所谓“枪手”之说,我的猜测居多,并没有什么铁证,只能参考一下。因此,我又静下心来,详细阅读了一些读者,特别是一些“大牛”级的读者的批评意见。希望能找出一些真正属于自己的错误,好修订本书。这也是本着为读者负责的态度。
但是,很奇怪,我发现不管是这些大牛,还是一些小牛,批评的意见大体有个总体思想,就是本书不符合C++开发的主流规范,显得“不标准”,“野路子”。这差不多也是目前批评本人和本书最主流的意见了。这让我这个作者莫名其妙,先不论这些读者是好心还是恶意,也不论他们批评的意见是否正确,但这些意见显然我无法接受。
原因很简单,请大家看本书的书名《0 bug -- C/C++商用工程之道》,这表明,本书源代码其实是用C和C++这两种语言开发完成的,并且,在本书中任何一处提到开发语言的时候,C一定排在C++语言前面,如“C和C++无错化开发方法”。
就我这个作者本人而言,在平时工作中,我比较喜欢同时使用C和C++两种语言混合开发,当然,以C为主。这一来是工程有时候需要,二来是我个人开发习惯,毕竟,我是先学会C语言,后来才学习的C++语言。
我想这也很好理解,从第一天开始,C和C++语言编译器就是兼容多种语言的,我们知道,几乎所有版本的C和C++语言编译器,都支持内嵌式汇编开发,这是合理的,因为工业控制中很多高速端口操作,需要汇编来完成。所以现代的C和C++编译器,也有逐渐大一统的趋势,无论是VC还是gcc,均支持混合语言编程,可以说,目前我们的主流编译器,基本上都兼容三种语言,C、C++和汇编。
因此,请各位读者就事论事,在阅读过程中,不要用纯正C++语言来考察本书的源代码,本来就不是主要用C++语言开发的。
其实,我分析了一下,在我的源代码中,C和C++的比例,差不多8:2,即80%左右使用C语言开发的。对于C++语言,我本人倾向于“有限使用”,这个呢,是我的习惯,我习惯于到具体功能点的实现,使用C模式,因为即使是C++语言,函数内部都还是OP的,即面向过程的,这符合实际需要,但在模块组织上,我比较喜欢使用C++的对象概念来封装,因为确实方便。
实际上,我曾经想过,是不是以本书定名就定为“C语言商用工程之道”,但显然也不现实,因为书中确实提到了对象。
根据我个人经验呢,这比较符合现代商用系统的开发模型,一个较为大型的系统,尤其是网络相关,很多时候,系统是多种语言的混合体,有C和C++的,还有PHP和Java的,客户端开发还经常使用JavaScript和C#等,这些,我认为都是合理的。这毕竟是一个全方位满足客户需求的综合开发时代。
另外,我这里也说说本书的源代码,很多读者知道,本书虽然内部包含大量带来,甚至包含一个并行开发工程库,但我并没有提供源代码下载服务,也不提供源码光盘,很多朋友都问我为什么,其实我是有原因的。
1、本书定位为一本工程实战书籍,我认为更多是讲思路,讲解法,即share我本人的一些经验,帮助大家在以后的工程开发中,能有一些解决思路,能解决具体问题。因此,我没有认为本书的源代码有多重要。
2、本书的源代码,我本人认为它是一种语言,由C和C++语言代码,以及相关注释,以及相关文字描述共同组成的一门语言,是程序员写给程序员看的一门语言,是用来讲清楚问题的,不是拿来就用的。就好比我们上学时的伪代码,是描述逻辑使用的,仅仅是我这个作者图省事,把自己的代码直接拿出来,做了注释就写到书里了。
3、我本意是写书,不是做开源,如果做开源,我直接在网上开个库供大家下载好了,不需要写书这么麻烦的。
4、本书中代码,最少的都有两年无故障连续运营历史,最多的有9年。但我仍然不认为这些代码就一点bug都没有。只能说,在过去的工程环境下,没有暴露出bug而已。这在书中已经说明了的。
5、本书讨论的0bug,我有专文说明,一来,0bug我认为是程序员应该有的一种追求,是目标,其实我本人也没有做到的,但二来,本书讨论的0bug,可能比大多数人讨论的严厉一点点,即产品卖出钱了,你把钱揣到包包里面,并且落袋为安,不会因为维护再花出去,这个叫做0bug。我想我已经说得够清楚了,此处再次重申一下,就不劳大家不断争论了。
此处呢,我作为作者,在此做个郑重声明,也希望各位读者和准读者朋友能精确看清楚本书的定位,以及写作的目标,有的放矢地购买和阅读本书,以便更好地学有所获。
==================================================
(以下文字,纯属虚构,如有雷同,实属巧合)
From: Mxxx Yxx <xxxxxx@gmail.com>
Date: 2010-2-2 12:28
Subject: Re: 关于程序中需要用锁的原因
To: xxxx <xxxxxx@gmail.com>
x先生你好
謝謝賜教。
我對於硬體的知識很有限。我以為cache是其中一個可能做成不同步的原因。
不同的 architecture 下的同步機制會有出入,行為會有出入,所以我認為編程時應該使用平台提供的方法,而不要去假設一些行為。
RISC 的 load/hit/store 是會造成不同步,但書中說的不一致的例子是: 一個 32-bit data 寫到
memory,只寫了16-bit,另一個 thread 就去讀取。這個情況我覺得在現代的系統裡應該不存在的。
所以我才建議,如果不需要就不要寫一些底層的機制,讓讀者明白一組內存/設備要同步時使用同步機制便可以了。
如果願意, 閣下可以把這討論放到豆瓣, 我轉載也可以的。
再次感謝x先生的來信指導。
在 2010年2月2日上午11:45,xxxx <xxxxxx@gmail.com> 寫道:
> 昨天看了您在豆瓣上的书评,以及后面的争论。关于0bug的
> 有个技术问题跟您讨论一下。
> 您说的关于程序中需要用锁的原因,是由于smp系统中存在cache。我觉得,这个论断是不正确的。
> 大多数现代的smp系统,包括多核、多CPU系统,应该都是在硬件解决了这个问题。
> 比如,小规模的系统,用总线侦听协议,如:MESI协议。
> 而大规模系统,则用目录协议来解决这个问题。
> 所以一般来说,在软件实现的互斥锁中,并不会有一个显式的cache同步指令。
> 即便是在没有cache的单CPU、单核系统中,也可能存在多线程之间的数据不一致的问题。
> 例如:我们有一个简单程序,一个线程循环执行i++,另外一个线程执行i--,两个线程的循环次数相等,这两个线程的循环次数足够的大的时候,运行完毕之后,i的结果可能不等于初始值。
> 这是因为,某些RISC体系的CPU,load和store指令是分开的,当一个线程执行了load之后,如果被另外一个线程打断,此时就会出现我们不期望的结果。
> 即便在x86上,也可能出现,因为对多数语言并不会去定义,i++这条语句应该对应一条什么样的汇编指令。
> 尽管大多数x86上的编译器会把i++编译成一条inc mem[imm]指令,但这个是不保证的。程序正确性不应依赖于某个特定编译器。
>
下面是MSN的讨论记录:
xx 说:
打扰
xx 说:
昨天看了您在豆瓣上的书评,以及后面的争论。
xx 说:
关于0bug的
xx 说:
有个技术问题跟您讨论一下。
xx 说:
您说的关于程序中需要用锁的原因,是由于smp系统中存在cache。我觉得,这个论断是不正确的。
xx 说:
大多数现代的smp系统,包括多核、多CPU系统,应该都是在硬件解决了这个问题。
xx 说:
比如,小规模的系统,用总线侦听协议,如:MESI协议。
xx 说:
而大规模系统,则用目录协议来解决这个问题。
xx 说:
所以一般来说,在软件实现的互斥锁中,并不会有一个显式的cache同步指令。
xx 说:
即便是在没有cache的单CPU、单核系统中,也可能存在多线程之间的数据不一致的问题。
xx 说:
例如:我们有一个简单程序,一个线程循环执行i++,另外一个线程执行i--,两个线程的循环次数相等,这两个线程的循环次数足够的大的时候,运行完毕之后,i的结果可能不等于初始值。
xx 说:
这是因为,某些RISC体系的CPU,load和store指令是分开的,当一个线程执行了load之后,如果被另外一个线程打断,此时就会出现我们不期望的结果。
xx 说:
即便在x86上,也可能出现,因为对多数语言并不会去定义,i++这条语句应该对应一条什么样的汇编指令。
xx 说:
尽管大多数x86上的编译器会把i++编译成一条inc mem[imm]指令,但这个是不保证的。
xx 说:
程序正确性不应依赖于某个特定编译器。
xx 说:
稍等,去开会。
xx 说:
回来再讨论。
xx 说:
hi
xx 说:
关于为什么要用锁的问题,可否这样解答。
xx 说:
在多线程环境下,多个线程之间共享内存中的对象。
xx 说:
程序运行的正确性,依赖每个线程对于内存对象的更改操作的原子性。
xx 说:
用锁的目的,就是为了保证原子性。
xx 说:
而非原子更改操作产生的原因是多方面的,如:肖先生所说的原因,多个byte分开操作,是一方面的原因。
xx 说:
您说的smp体系下cache不一致性的问题,是导致这个问题的另外一个原因。
xx 说:
究竟应该用何种类型的锁,是由编程环境所定义的内存一致性模型决定的。
xx 说:
对象原子性被破坏的问题,是表现在不同层次上的。
xx 说:
他们之间又是相互联系的,如:底层内存访问的不一致,可能导致编程语言和操作系统层面不一致。
xx 说:
例如,您说的cache一致性问题,可能被传导到上层编程语言的层面。
Mxxx 说:
要正確說明為甚麼要用同步機制, 可能是挺困難的. 我有空的看看相關書籍的說法吧.
xx 说:
至于究竟应该用那种类型的锁,是由编程环境决定的,例如:如:PowerPC体系结构采用“释放一致性”模型,是比较松散的。
Mxxx 说:
atomicity 和 consistency 好像是兩個不同的 quality
xx 说:
而其模型,则要严格些。
xx 说:
是的,这是不同的概念。
xx 说:
似乎你对中文技术词汇,不是很熟悉
Mxxx 说:
小時候主要看台灣的, 現在又看內地的, 都混亂了
xx 说:
关于那本书,我没看过
xx 说:
但是那个人,我认识
xx 说:
我觉得要全面评价一本书籍,是比较困难的事情。
Mxxx 说:
這當然了, 不然就不會有評論家了.
xx 说:
您之所以产生“失望”的感觉,是因为那本书不太适合您。
Mxxx 说:
我希望可以比較客觀地在總結裡寫本書的優缺點
xx 说:
如果让一个成年人去读儿童读物,就会比较“失望”
xx 说:
当然,也并不是说儿童读物没有价值。
Mxxx 说:
其實我看前三章時, 真的感到書裡的不嚴謹, 只是靠經驗去解決問題, 也叫讀者這麼做.
xx 说:
这是他的习惯,我并不赞同。
xx 说:
但,返回头来说,这本书可能会让另外一些人学习到一些经验。也未尝不是好事。
xx 说:
我相信,每个人读书,对其内容都是选择性的读。不会是全盘接受的。
xx 说:
如果肖先生能够再谦虚一点,也许更好。
xx 说:
打扰了。
--
Mxxx Yxx
2010-02-04 21:43:50 knight_stalker
我记得 n 年前就有讲 Intel 多核编程的书了 …… 大学里的做并行计算的人灰常的多 …… 不过和肖老师想的并行有点出入。。2010-02-04 22:27:01 iLRainyday
并行计算、并行编译这些课多是在研究生阶段开设的,肖老师不知道是很正常的。2010-02-05 09:27:05 肖舸
话说某日,我正在归妹位看书。突然,一声怒喝,某人左手板砖,右手大刀片子,身上挂着要你命3000,奔着我就过来了,吓我一跳。
他身后,还跟着一群打了鸡血的小公鸡,一个个鸡冠子竖老高,红得发紫,亢奋得嗷嗷直叫。这叫助威团。
我说大事不好,正待摆出架势接招。
没想到某人行至中途,突然刀锋一转,直接砍到无妄位的电线杆子上去了。
可怜的电线杆,脑门上挂了个“C”。
随后,板砖,脏水,烂西红柿,臭鸡蛋... ...
电线杆惨不忍睹!
我看了一下,觉得无趣,就走了... ...
一个星期以后,我突发奇想,就回去看看。
电线杆子还在,某人还在继续劈砍,不过,显然后继乏力。
助威团呢,还剩下小鸡两三只。鸡冠子也垂下来了。毕竟,从生理学上讲,雄起得太久,会钙化的,呵呵。
我看了一下,觉得无趣,就又走了... ...
... ...
... ...
“收工!”,随着导演一声令下,摄影棚一片忙乱。
“碰”,道具关闭了电闸,灯火通明的摄影棚暗了下来。
人声渐行渐远,摄影棚安静下来。
墙边的小门被微风吹开,一缕路灯的灯光,射了进来。
凄白的灯光,射到摄影棚中心的道具“C”电线杆上。
杆下靠坐着某人,嘴里喃喃地说:
“哥拍的不是C,哥只是想成为传说... ...”
----仅以此文,纪念2010年春,某人以C++拍C,以及替伪代码debug的神勇之战
2010-02-05 19:19:03 redleaves
inline int _Get0(void){
int nRet = rand();
return nRet^nRet;
}
关于这个代码,我估计作者原意是强制编译器不使用立即数而是直接用XOR REG,REG这种方式得到0这个值.在早这种手法在早期的汇编中会经常用,这样生成的代码会更短更快.不过现代编译器对于这种问题已经可以自动优化了.加这种代码只是徒增烦恼...
2010-02-05 20:24:36 Milo
@redleaves這個我是知道的,不過問題是為甚麼寫個 return 0 的 inline 函數...
2010-02-06 13:41:25 魏理布赫
inline int GetRandomBetween(int nBegin, int nEnd){
return nBegin + (int)((long)rand() * (nEnd - nBegin) / RAND_MAX);
}
也不会产生 [nBegin, nEnd) 之间的均匀分布(伪)随机数,因为rand()的结果不是无穷大,这在 nEnd - nBegin 很大时会体现比较明显。并且,long在32环境下一般都是32位,64位环境下msvc中long也是32位,与int相同。
不过你这个已经比那个作者严谨多了。
2010-02-06 13:47:37 魏理布赫
——————————————————————————————#define CON_PRINTF /\
/printf
~~~~~~~~~~~~~~~~~~~~~~~~~~
这是很匪夷所思的用法,作者应该是期待期待宏展开后变成 // 注释
微软头文件中有一种写法,类似:
#define debug_printf 1 ? 0 : printf
这在不支持变参宏的系统中几乎是唯一用法
2010-02-06 14:32:41 Milo
@魏理布赫謝謝提點. 應該使用 long long,或是全部改成 int32_t / int64_t的方式。
不過我不太明白你所指的, rand() 結果不是無窮大, 所以不能產生均勻分佈的意思。願聞其詳。
2010-02-07 10:24:34 hiessu
@Miloreturn nBegin + (int)((long long)rand() * (nEnd - nBegin) / RAND_MAX);
這個函式傳回 [nBegin, nEnd) 半開區間 (不包括 nEnd)中的隨機數。
---------------------------------------------------------------------------------------
rand() == RAND_MAX ? 不会取到 nEnd?
我在前面有回复, 你可能没看到。我认为应该是
int RandomInteger(int low, int high) {
double d = double(rand()) / (double(RAND_MAX) + 1);
return low + int(d * (high - low));
}
《Accelerated C++》 第七章的写法是:
// return a random integer in the range [0, n)
int nrand(int n)
{
if (n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");
const int bucket_size = RAND_MAX / n;
int r;
do r = rand() / bucket_size;
while (r >= n);
return r;
}
把数分布到相应的第n个 ([0, n))bucket 上。道理一样。这里的 do while 就是避免取到n吧?
2010-02-07 12:18:38 Milo
@hiessu剛看過reference,發現是我記錯了 rand() 的回傳值應該是 [0, RAND_MAX],不是我以為的 [0, RAND_MAX)。
我認為可以不用浮點小數而做到同樣精度的話應該不用。我跟據你的 + 1 寫法作出修改吧。我覺得 Accelerated C++ 版本要用 do..while() 不太好。有機會我在看看參考寫篇 blog 吧。
2010-02-07 13:17:07 knight_stalker
很多库代码都把 rand 实现得非常好了,弄这么细不如在主板上装个热电子发生器 ……2010-02-07 13:25:51 Milo
@night_stalker 不是要個甚麼放射性源嗎? :)2010-02-07 18:48:42 knight_stalker
混沌现象不需要到量子层面就能发生,焊个电阻已经够随机了。。。2010-02-07 18:49:27 hiessu
@Milo"我認為可以不用浮點小數而做到同樣精度的話應該不用。" 这个没什么经验,我搜索的结果都是用double的。不清楚差异性在哪?
等你写blog讲讲吧,有点小题大作吧,呵呵。
2010-02-09 14:37:58 魏理布赫
@Milo不過我不太明白你所指的, rand() 結果不是無窮大, 所以不能產生均勻分佈的意思。願聞其詳。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果 rand() 在 [0, n) 之间均匀分布
那么如果 n % m != 0
则 rand()%m 在 [0, m) 不是均匀分布
而如果 rand() 在 [0, infinate) 均匀分布,那么对任意m>=1我们就可以期望 rand() % m 均匀分布,而这实际上不可能。
工程上,如果 m 相比 n 足够小,我们才能忽略这一点
2010-02-09 14:55:39 魂回首
严重支持Milo,用事实说话,并且对事不对人。相比之下的著书人....2010-02-09 20:00:18 Milo
@魏理布赫你說的是對的, 所以我的建議裡是用除數不用餘數.
2010-02-11 17:07:21 mahoo
另外有一个问题也是很困扰人的,就是Milo所使用的技术名词与大陆这边使用的技术名词不是很一致,建议关键问题点上尽量使用英文.....文中的"同部"是否是"同步"的意思,即一般所说的synchronize?
2010-02-11 17:26:32 Milo
@mahoo 筆誤, 去改正2010-02-23 20:42:51 Vincent.Fon
2010-02-02 07:09:23 night_stalker >小时候学编程,因为看的旁门左道中文书太多 bug 而走了不少弯路 ……>蛋 bug 这名字太具忽悠性,一定得踩 ……
tonggan
2010-03-05 21:50:32 无名之辈
各打五十大板,回家闭门思过去!其一,要看清别人用某些东西的目的,而不要只就那东西本身发评论;
其二,人不知而不愠,不亦君子乎?
洗洗睡了,睡眠严重不足!!!
2010-03-06 14:27:29 取啥名呢
2010-03-05 21:50:32 无名之辈各打五十大板,回家闭门思过去!
其一,要看清别人用某些东西的目的,而不要只就那东西本身发评论;
=======================
作者自己说不明白,怎么可以怪读者
再说了,错就是错,怎么不可以指出来
2010-04-09 17:04:40 后来
milo帮了肖舸大忙,他的书应该会大卖了吧2010-05-07 23:15:54 rush
来晚了2010-05-16 14:21:52 Kirby Zhou
关于 char *asctime(const struct tm *tm);对于绝大部分的实现,都会是线程安全的,但不是可重入的
类似的函数有char *inet_ntoa(struct in_addr in)等
具体来讲
这类函数使用了thread local storage,为每个线程建立了一个私有的缓冲区。这样不同的线程得到的返回值是不一样的。
伪代码类似这样:
char *asctime(const struct tm *tm)
{
tid_t tid = get_thread_id();
char* buf = find_buffer_by_thread_id(tid);
/* do something wit buf */
return buf;
}
2010-05-29 20:46:18 Wang Feng
发现楼主新的博文了,觉得没有必要再与这等态度的作者纠缠了2010-06-05 18:11:47 yumeyao
P. 199 這個我真的看不明白:inline int _Get0(void)
{
int nRet = rand();
return nRet^nRet;
}
為甚麼要這麼做才能獲得一個零呢? 如果必須調用 rand(),也在之後直接 return 0; 就可以吧?
-----------------------------------------------------
我猜想,作者是为了让编译后的代码为 XOR EAX,EAX而不是MOV EAX,0,故使用上面的语句。(当然,还必须顺承作者必须调用rand()的推测)
但是我所接触到的现代编译器都会自动的把return 0或者var=0这样的语句自动设成XOR reg,reg(假设上面的var是寄存器变量),所以作者的代码仍然没有意义。
响应楼主号召,不对作者的知识认识作猜测。
2010-08-12 11:53:59 xxx
你是哪的人,香港的?台湾的,你能首先不装B不用繁体字吗?2010-08-12 12:00:28 xxx
我发现好多你质疑的地方根本是你自己没读懂,或者说你没那个工程经验,不知道作者这么用的目的和奥妙所在就在那自行评论了,一些没什么工程经验的C++学生跟着SB一样的附和,对原作者真是侮辱,声明啊,我和肖舸一点关系都没有,有一点点关系我就出门被车撞死.2010-08-12 12:06:28 xxx
你有的文字根本不是在高技术,纯粹在挑文字,其实我很鄙视你.2011-06-19 01:04:02 白洁之心
呵呵后,围观73L~75L的某人马甲> 我来回应