[英]Explaining this passage in “About size_t and ptrdiff_t”
在Andrey Karpov的博客文章“關於size_t
和ptrdiff_t
”中,他總結道:
如讀者所見,使用ptrdiff_t和size_t類型為64位程序提供了一些優勢。 但是,這不是用size_t替換所有無符號類型的全面解決方案。 首先,它不能保證在64位系統上程序的正確運行。 其次,很可能由於此替換,將出現新的錯誤,將破壞數據格式兼容性,依此類推。 您不要忘記,替換之后,程序所需的內存大小也會大大增加。 增加必要的內存大小將減慢應用程序的工作,因為緩存將存儲更少的正在處理的對象。
我不理解這些說法,也看不到文章中提到的內容,
“很可能由於這種替換,將出現新的錯誤,將違反數據格式兼容性,等等。”
在遷移和類型遷移導致錯誤之前,怎么可能沒有錯誤? 尚不清楚何時類型( size_t
和ptrdiff_t
)比它們要替換的更具限制性。
您不要忘記,替換之后,程序所需的內存大小也會大大增加。
我不清楚所需的內存大小將如何或為何“大大”增加,或者根本不會增加? 但我知道,如果這樣做的話,安德烈的結論將隨之而來。
該文章包含非常可疑的主張。
首先, size_t
是sizeof
返回的sizeof
。 uintptr_t
是一個整數類型,可以存儲任何指向void
指針 。
該文章聲稱size_t
和uintptr_t
是同義詞。 他們不是。 例如,在具有大型內存模型的分段MSDOS上,數組中元素的最大數量將適合16位的size_t
,但是指針需要32位。 它們現在是我們常見的Windows,Linux平面內存模型的代名詞。
更糟糕的是聲稱可以將指針存儲在ptrdiff_t
,或者它與intptr_t
是同義的:
size_t
和ptrdiff_t
的大小始終與指針的大小一致。 因此,應將這些類型用作大型數組的索引,指針的存儲和指針算術。
那根本不是真的。 ptrdiff_t
是指針減法的值的類型,但是僅當兩個指針都指向同一對象或緊隨其后時才定義指針減法,而不僅僅是內存中的任何地方。
另一方面,可以將ptrdiff_t
選擇為大於 size_t
這是因為,如果您的數組大小大於MAX_SIZE / 2
元素,則從指向最后一個元素的指針中減去指向第一個元素的指針或將其超出如果ptrdiff_t
與size_t
寬度相同,則未定義的行為。 確實,該標准確實指出size_t
只能為16位寬,但是ptrdiff_t
必須至少為17 ]( http://port70.net/~nsz/c/c11/n1570.html#7.20.3 )。
在Linux上, ptrdiff_t
和size_t
具有相同的大小-並且可以在大於PTRDIFF_MAX
元素的32位Linux上分配一個對象 。 正如評論中指出的那樣,標准不需要ptrdiff_t
與size_t
處於同一等級,盡管這樣的實現是純粹的邪惡。
如果要遵循建議並使用size_t
和ptrdiff_t
來存儲指針 ,則肯定不能正確執行 。
至於說
您不要忘記,替換之后,程序所需的內存大小也會大大增加。
我會爭辯說-與一般的64位對齊,堆棧的對齊和移至64位環境中固有的64位指針所引起的消耗增加相比,內存需求的增長將是相當適度的。
至於說
“很可能由於這種替換,將出現新的錯誤,將違反數據格式兼容性,等等。”
的確是這樣,但是如果您正在編寫此類錯誤代碼,很可能會意外地 “修復”流程中的舊錯誤,例如有signed/unsigned int
示例:
int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B); //Error
printf("%i\n", *ptr);
原始代碼和新代碼都將具有未定義的行為 (超出范圍訪問數組元素),但是新代碼在64位平台上也似乎是“正確的”。
那么任何更改都可能會引入錯誤。 具體來說,我可以想象改變大小可能會在應用類型不太嚴格的地方中斷(例如,假設int或long與未使用int的指針相同)。 寫入協議的任何二進制結構都無法直接讀取,並且任何RPC都可能失敗,具體取決於協議。
隨着大多數內存對象的大小增加,內存需求將明顯增加。 大多數數據將在64位邊界上對齊,這意味着更多的“空洞”。 堆棧使用量將增加,可能導致更頻繁的緩存未命中。
盡管所有概括都是對還是錯,但找出答案的唯一方法是對手頭的系統進行一些適當的分析。
一般而言,使用size_t
和ptrdiff_t
比使用普通unsigned int
和int
更為可取。 size_t
和ptrdiff_t
幾乎是編寫健壯且可移植的程序的唯一方法。
但是:沒有免費的午餐。 正確地使用size_t
需要一些工作-就是說,如果您知道自己在做什么,則比不使用size_t
來達到相同的結果所需的工作要少。
另外, size_t
存在無法使用%d
或%u
打印的問題。 理想情況下,您想使用%zu
,但可悲的是,並非所有實現都支持它。
如果您有一個不使用size_t
的大型且編寫錯誤的程序,則可能有很多錯誤。 其中一些錯誤將被掩蓋或解決。 如果您嘗試將其更改為使用size_t
,則該程序的一定數量的解決方法將失敗,可能會發現曾經隱藏的錯誤。 最終,您將解決這些問題,並實現所需的更健壯,更可靠和更便攜的程序,但是過程將是艱難的。 我懷疑這就是作者的意思,“很可能由於這種替換,將出現新的錯誤”。
將程序更改為使用size_t
就像在所有正確的位置嘗試添加const
一樣。 您進行了您認為需要進行的更改,然后重新編譯,並且收到一堆錯誤和警告,然后修復並重新編譯,又收到了許多錯誤和警告等。這至少是令人討厭的事情,並且有時需要大量工作。 但是,如果要使代碼更健壯和可移植,這通常是唯一的方法。
問題的很大一部分是使編譯器滿意。 它會警告一堆東西,並且您通常會想修復它抱怨的所有內容,即使它抱怨的有些棘手且不太可能引起問題。 但是說“是的,我可以忽略此特定警告”是很危險的,因此最后,正如我所說,您通常會想解決所有問題。
作者最引人注目的主張是
該程序所需的內存大小也將大大增加。
我懷疑這是一種誇張-在大多數情況下,我懷疑內存會“大大”增加-但可能至少會增加一點。 問題在於,在64位系統上, size_t
和ptrdiff_t
可能是64位類型。 如果出於某種原因,您有很多這樣的數組或包含這些數組的結構很大的數組,並且之前曾經使用過某些32位類型(也許是plain int
或unsigned int
),是的,您將看到一個內存增加。
然后您將要問, 我真的需要能夠描述64位大小嗎? 64位編程為您提供兩件事:(a)能夠尋址大於4Gb的內存,以及(b)具有大於4Gb的單個對象的能力。 如果您要使用的總數據量大於4Gb,但是您永遠不需要一個大於4Gb的對象,並且您不想一次從文件中讀取大於4Gb的數據(使用單read
或fread
調用,這是),你並不真的需要無處不在的64位大小的變量。
因此,為避免膨脹,您可以做出明智的選擇,例如在某些地方使用unsigned int
(甚至unsigned short
)而不是size_t
。 作為一個簡單的例子,如果您有
size_t x = sizeof(int);
printf("%zu\n", x);
您可以將其更改為
unsigned int x = sizeof(int);
printf("%u\n", x);
不會造成任何可移植性損失,因為我可以完全保證您的代碼永遠不會在具有34359738368位int
的計算機上運行(或者至少在我們的生命周期內不行:-))。
但是,這最后一個例子雖然微不足道,但它也說明了其他容易引起注意的問題。 相似的代碼
unsigned int x = sizeof(y);
printf("%u\n", x);
顯然不是那么安全,因為無論y
是多少,都有可能太大,以至於其大小無法容納無符號int。 因此,如果您或您的編譯器真的在乎類型正確性,則在將size_t
分配給unsigned int
時,可能會出現有關數據可能丟失的警告。 要關閉這些警告,您可能需要顯式強制轉換,例如
unsigned int x = (unsigned int)sizeof(int);
可以說,這個演員陣容非常合適。 編譯器在假設任何對象可能真的很大的前提下進行操作,任何將size_t
塞入unsigned int
嘗試都可能會丟失數據。 演員表表明您已經考慮過這種情況:您是在說:“是的,我知道這一點,但是在這種情況下,我知道它不會溢出,因此請不要再對這一情況發出警告,但是請對其他任何警告我,可能不是那么安全。”
PS:我被否決了,因此,如果我給人留下了錯誤的印象,請讓我明確指出(正如我在開始段落中所述), size_t
和ptrdiff_t
非常ptrdiff_t
。 通常,有充分的理由使用它們,沒有充分的理由不使用它們。 (為此,Karpov也沒有說不使用它們,只是強調了可能會遇到的一些問題。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.