为什么size_t重要?为什么不直接用unigned long int 代替?以及size_t、ptrdiff_t、socklen_t数据类型
背景:
我們先來看一張基本數(shù)據(jù)類型在各個(gè)平臺(tái)中字節(jié)長(zhǎng)度表:
根據(jù)上表,我們可以看到指針的字節(jié)長(zhǎng)度:
- 16 bit系統(tǒng)中,占用2字節(jié)(arduino nano)
- 64 bit系統(tǒng)中,占用8字節(jié)(vc64/ios模擬器)
- 32 bit系統(tǒng)中,占用4字節(jié)(除了上述3個(gè)系統(tǒng)外)
這種與機(jī)器類型相關(guān)的數(shù)據(jù)類型,我們可以稱為機(jī)器相關(guān)數(shù)據(jù)類型
問: 現(xiàn)在有個(gè)需求,我需要跨平臺(tái)的使用統(tǒng)一數(shù)據(jù)類型來表示指針地址范圍,我該如何辦?
答: 兩種方式:
-
typedef long long int64;
使用最大字節(jié)長(zhǎng)度的數(shù)據(jù)類型來容納各個(gè)平臺(tái)下的地址數(shù)據(jù) -
使用c/c++預(yù)先定義的機(jī)器相關(guān)數(shù)據(jù)類型:?size_t/ptrdiff_t
使用size_t可能會(huì)提高代碼的可移植性、有效性或者可讀性,或許同時(shí)提高這三者
size_t的取值range是目標(biāo)平臺(tái)下最大可能的數(shù)組尺寸
在標(biāo)準(zhǔn)C庫(kù)中的許多函數(shù)使用的參數(shù)或者返回值都是表示的用字節(jié)表示的對(duì)象大小,比如說malloc(n) 函數(shù)的參數(shù)n指明了需要申請(qǐng)的空間大小,還有memcpy(s1, s2, n)的最后一個(gè)參數(shù),表明需要復(fù)制的內(nèi)存大小,strlen(s)函數(shù)的返回值表明了以’\0’結(jié)尾的字符串的長(zhǎng)度(不包括’\0’),其返回值并不是該字符串的實(shí)際長(zhǎng)度,因?yàn)橐サ簟痋0’。
或許你會(huì)認(rèn)為這些參數(shù)或者返回值應(yīng)該被申明為int類型(或者long或者unsigned),但是事實(shí)上并不是。C標(biāo)準(zhǔn)中將他們定義為size_t。標(biāo)準(zhǔn)中記載malloc的申明應(yīng)該出現(xiàn)在,定義為:
memcpy和strlen的申明應(yīng)該出現(xiàn)在中:
void *memcpy(void *s1, void const *s2, size_t n); size_t strlen(char const *s);size_t還經(jīng)常出現(xiàn)在C++標(biāo)準(zhǔn)庫(kù)中,此外,C++庫(kù)中經(jīng)常會(huì)使用一個(gè)相似的類型size_type,用的可能比size_t還要多。
據(jù)我所知,大部分的C和C++程序員害怕這些庫(kù)使用size_t,因?yàn)樗麄儾恢纒ize_t代表什么或者為什么這些庫(kù)需要使用它,歸根結(jié)底,原因在于他們什么時(shí)候什么地方需要用到它。
可移植性問題
早期的C語(yǔ)言(由Brian Kernighan 和 Dennis Ritchie 在The C Programming Language書中所寫,Prentice-Hall, 1978)并沒有提供size_t類型,C標(biāo)準(zhǔn)委員會(huì)為了解決移植性問題將size_t引入,舉例如下:
讓我們來寫一個(gè)可移植的標(biāo)準(zhǔn)memcpy函數(shù),我們將會(huì)看到一些不同的申明和它們?cè)诓煌脚_(tái)不同大小的地址空間上編譯下的情況。
回憶memcpy(s1, s2, n)函數(shù),它將s2指向地址開始的n個(gè)字節(jié)拷貝到s2指向的地址,返回s1,這個(gè)函數(shù)可以拷貝任何數(shù)據(jù)類型,所以參數(shù)和返回值的類型應(yīng)該為可以指向任何類型的void*,同時(shí),源地址不應(yīng)該被改變,所以第二個(gè)參數(shù)s2類型應(yīng)該為const void*,這些都不是問題。
真正的問題在于我們?nèi)绾紊昝鞯谌齻€(gè)參數(shù),它代表了源對(duì)象的大小,我相信大部分程序員都會(huì)選擇int:
使用int類型在大部分情況下都是可以的,但是并不是所有情況下都可以。int是有符號(hào)的,它可以表示負(fù)數(shù),但是,大小不可能是負(fù)數(shù)。所以我們可以使用unsigned int代替它讓第三個(gè)參數(shù)表示的范圍更大。
在大部分機(jī)器上,unsigned int的最大值要比int的最大值大兩倍,比如說再也給16位的機(jī)器上,unsigned int的最大值為65535,int的最大值為32767。
盡管int類型的大小依賴于C編譯器的實(shí)現(xiàn),但是在給定的平臺(tái)上int對(duì)象的大小和unsigned int對(duì)象的大小是一樣的。因此,使用unsigned int修飾第三個(gè)參數(shù)的代價(jià)與int是相同的:
這樣似乎沒有問題了,unsigned int可以表示最大類型的對(duì)象大小了,這種情況只有在整形和指針類型具有相同大小的情況下,比如說在IP16中,整形和指針都占2個(gè)字節(jié)(16位),而在IP32上面,整形和指針都占4個(gè)字節(jié)(32位)。(參見下面C數(shù)據(jù)模型表示法)
C數(shù)據(jù)模型表示法
最近,我偶然發(fā)現(xiàn)幾篇文章,他們使用簡(jiǎn)明的標(biāo)記來表述不同目標(biāo)平臺(tái)下c語(yǔ)言數(shù)據(jù)的實(shí)現(xiàn)。我還沒有找到這個(gè)標(biāo)記的來源,正式的語(yǔ)法,甚至連名字都沒有,但他似乎很簡(jiǎn)單,即使沒有正規(guī)的定義也可以很容易使用起來。這些標(biāo)記的一邊形式形如:
I nI L nL LL nLL P nP。 ??
其中每個(gè)大寫字母(或成對(duì)出現(xiàn))代表一個(gè)C的數(shù)據(jù)類型,每一個(gè)對(duì)應(yīng)的n是這個(gè)類型包含的位數(shù)。I代表int,L代表long,LL代表long long,以及P代表指針(指向數(shù)據(jù),而不是函數(shù))。每個(gè)字母和數(shù)字都是可選的。 ??
例如,I16P32架構(gòu)支持16位int和32位指針類型,沒有指明是否支持long或者long long。如果兩個(gè)連續(xù)的類型具有相同的大小,通常省略第一個(gè)數(shù)字。例如,你可以將I16L32P32寫為I16LP32,這是一個(gè)支持16位int,32位long,和32位指針的架構(gòu)。 ?
標(biāo)記通常把字母分類在一起,所以可以按照其對(duì)應(yīng)的數(shù)字升序排列。例如,IL32LL64P32表示支持32位int,32位long,64位long long和32位指針的架構(gòu);然而,通常寫作ILP32LL64。 ?
不幸的是,這種memcpy的申明在I16LP32架構(gòu)上(整形是16-bit 長(zhǎng)整形和指針類型時(shí)32-bits)顯得不夠用了,比如說摩托羅拉第一代處理器68000,在這種情況下,處理器可能拷貝的數(shù)據(jù)大于65535個(gè)字節(jié),但是這個(gè)函數(shù)第三個(gè)參數(shù)n不能處理這么大的數(shù)據(jù)。
什么?你說很容易就可以改正?只需要把memcpy的第三個(gè)參數(shù)的類型修改一下:
你可以在I16LP32目標(biāo)架構(gòu)上使用這個(gè)函數(shù)了,它可以處理更大的數(shù)據(jù)。而且在IP16和IP32平臺(tái)上效果也還行,說明它確實(shí)給出了memcpy的一種移植性較好的申明。但是,在IP16平臺(tái)上相比于使用unsigned int,你使用unsigned long可能會(huì)使你的代碼運(yùn)行效率大打折扣(代碼量變大而且運(yùn)行變慢)。
在標(biāo)準(zhǔn)C中規(guī)定,長(zhǎng)整形(無(wú)論無(wú)符號(hào)或者有符號(hào))至少占用32位,因此在IP16平臺(tái)上支持標(biāo)準(zhǔn)C的話,那么它一定是IP16L32 平臺(tái)。這些平臺(tái)通常使用一對(duì)16位的字來實(shí)現(xiàn)32位的長(zhǎng)整形。在這種情況下,移動(dòng)一個(gè)長(zhǎng)整形需要兩條機(jī)器指令,每條移動(dòng)一個(gè)16位的塊。事實(shí)上,這個(gè)平臺(tái)上的大部分的32位操作都需要至上兩條指令。
因此,以可移植性為名將memcpy的第三個(gè)參數(shù)申明為unsigned long而降低某些平臺(tái)的性能是我們所不希望看到的。使用size_t可以有效避免這種情況。
size_t類型是一個(gè)類型定義,通常將一些無(wú)符號(hào)的整形定義為size_t,比如說unsigned int或者unsigned long,甚至unsigned long long。每一個(gè)標(biāo)準(zhǔn)C實(shí)現(xiàn)應(yīng)該選擇足夠大的無(wú)符號(hào)整形來代表該平臺(tái)上最大可能出現(xiàn)的對(duì)象大小。
使用size_t
size_t的定義在<stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>和<wchar.h>這些標(biāo)準(zhǔn)C頭文件中,也出現(xiàn)在相應(yīng)的C++頭文件,?等等中,你應(yīng)該在你的頭文件中至少包含一個(gè)這樣的頭文件在使用size_t之前。
? ? ? ?包含以上任何C頭文件(由C或C++編譯的程序)表明將size_t作為全局關(guān)鍵字。包含以上任何C++頭文件(當(dāng)你只能在C++中做某種操作時(shí))表明將size_t作為std命名空間的成員。
? ? ? ?根據(jù)定義,size_t是sizeof關(guān)鍵字(注:sizeof是關(guān)鍵字,并非運(yùn)算符)運(yùn)算結(jié)果的類型。所以,應(yīng)當(dāng)通過適當(dāng)?shù)姆绞铰暶鱪來完成賦值:
n = sizeof(thing);考慮到可移植性和程序效率,n應(yīng)該被申明為size_t類型。類似的,下面的foo函數(shù)的參數(shù)也應(yīng)當(dāng)被申明為sizeof:
foo(sizeof(thing)); 參數(shù)中帶有size_t的函數(shù)通常會(huì)含有局部變量用來對(duì)數(shù)組的大小或者索引進(jìn)行計(jì)算,在這種情況下,size_t是個(gè)不錯(cuò)的選擇。?
適當(dāng)?shù)厥褂胹ize_t還會(huì)使你的代碼變得如同自帶文檔。當(dāng)你看到一個(gè)對(duì)象聲明為size_t類型,你馬上就知道它代表字節(jié)大小或數(shù)組索引,而不是錯(cuò)誤代碼或者是一個(gè)普通的算術(shù)值。?
?
size_t、ptrdiff_t、socklen_t數(shù)據(jù)類型在不同平臺(tái)的大小
1. size_t/ptrdiff_t:
printf("size_t bytes = %d\n" ,sizeof(size_t)); printf("ptrdiff_t bytes = %d\n" ,sizeof(ptrdiff_t));-
的確如此,size_t/ptrdiff_t數(shù)據(jù)類型是和機(jī)器相關(guān)的。其sizeof字節(jié)長(zhǎng)度和指針字節(jié)長(zhǎng)度是一致的。
-
size_t/ptrdiff_t之間的區(qū)別如下(以windows定義為例,其他系統(tǒng)一樣):
size_t 是無(wú)符號(hào)(unsigned)整數(shù),而ptrdiff_t是有符號(hào)整數(shù)
-
size_t更適合表達(dá)指針地址值。指針地址取值范圍 = size_t取值范圍。
-
ptrdiff_t從字面意思就能知道: 兩個(gè)指針地址(無(wú)正負(fù))的差(有正負(fù))
我們知道,指針之間具有加減操作,表示指針的移動(dòng)
void printChineseStringPtrdiff() {char str[] = "隨風(fēng)而行之青衫磊落險(xiǎn)峰行";char *pstart = str;char *pend = str + strlen(str);ptrdiff_t difp = pend - pstart;printf("%d\n", difp); }上述代碼在vc32/64中輸出24,提出兩個(gè)問題:
2. socklen_t:
- windows下定義在頭文件:#include<ws2tcpip.h>中
- ios/linux定義在頭文件: #include <sys/socket.h>中
- android ndk中,定義為__socklen_t而不是socklen_t
- socklen_t必須要和當(dāng)前機(jī)器的int類型具有一致的字節(jié)長(zhǎng)度,根據(jù)上面幾張圖,不管是32/64位系統(tǒng),socklen_t都是4byte
? ? ? ? ?數(shù)據(jù)類型”socklen_t”和int應(yīng)該具有相同的長(zhǎng)度,否則就會(huì)破壞 BSD套接字層的填充。POSIX開始的時(shí)候用的是size_t, LinusTorvalds(他希望有更多的人,但顯然不是很多) 努力向他們解釋使用size_t是完全錯(cuò)誤的,因?yàn)樵?4位結(jié)構(gòu)中 size_t和int的長(zhǎng)度是不一樣的,而這個(gè)參數(shù)的長(zhǎng)度必須和int一致,因?yàn)檫@是BSD套接字接口標(biāo)準(zhǔn)。最終POSIX的那幫家伙找到了解決的辦法,那就是創(chuàng)造了一個(gè)新的類型”socklen_t”。Linus Torvalds說這是由于他們發(fā)現(xiàn)了自己的錯(cuò)誤但又不好意思向大家伙兒承認(rèn),所以另外創(chuàng)造了一個(gè)新的數(shù)據(jù)類型 。
?
?
參考文章:
Why size_t matters
About size_t and ptrdiff_t
簡(jiǎn)書-隨風(fēng)而行之青衫磊落險(xiǎn)峰行
總結(jié)
以上是生活随笔為你收集整理的为什么size_t重要?为什么不直接用unigned long int 代替?以及size_t、ptrdiff_t、socklen_t数据类型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Boost基础篇——安装
- 下一篇: 大学计算机基础完整性约束,大一大学计算机