數制、碼制、位、字節、字長及内存對齊等概念具有一定的相關性:
1 數制:可用符号數及模數人類最常用的便是10進制了,然後還有計時的60進制,月份的12進制。
計算機使用的是2進制,以及輔助的16進制與8進制,16和8進制與2的乘幂相關。3個二進制位對位一個8進制位,4個二進制位對應一個16進制位。
對于n進制,有如下規則:
符号數量:n-1個;
2進制:0,1; 8進制:0,1,2,3,4,5,6,7; 10進制:0,1,2,3,4,5,6,7,8,9; 16進制:0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F;
如何表示n呢?需要兩位,也就是10來表示n進制的n,n就是n進制的模數,當某一個數m與n進行求模運算時,其結果都會小于n,如m%n的結果小于n。
我們知道,計算機采用二進制,是因為其邏輯元件最容易實現,具有最佳的穩定性。其理論原理來自于布爾代數和香農提出的布爾代數在開關電路的實現。0和1可以用繼電器、晶體管的開關或電容電信号的有無來實現,且通過門電路(與門、或門、非門等)實現布爾代數。
如果單就數字組合來說,哪一種進制的效率最高?先問一個問題:
假設a*b=N
當a取什麼值時,a^b的值最大?(a,b可以是小數)。
設f(x)=x^(K/x),x>0,K是大于0的常數。對f求導,即可得x=e時f取極大值,也是最大值。
e=2.71828……
與e最接近的整數不是2而是3。
二進制存儲的計算機,一個32位的的機器使用了32個1和0,也就是64個元素,可以表達2^32=4.29*10^9個精度的數字。同樣是64個元素,假設表達成3^21.3=1.45*10^10,即使是3^21也有1.05*10^10個精度,也就是說,如果用三進制,可以有更多的組合,而四進制呢?組合數又變小了(其實e進制有最多的組合數)。
但3進制的硬件卻比較難以實現,而二進制的組合效率僅次于三進制,而硬件實現最簡單和穩定,所以二進制是天然的計算機最佳數制了,正如黃金是天然的貨币一樣。
前蘇聯曾搞過的三進制計算機:在一般情況下,命題不一定為真或假,還可能為未知。在三進制邏輯學中,符号1代表真;符号-1代表假;符号0代表未知。三進制邏輯電路的電壓存在着三種狀态:正電壓(1)、零電壓(0)和負電壓(-1)。
2 碼制:萬物編碼一個以上的符号通過組合規則便可以描述和表示複雜的信息了,如摩斯密碼或電碼,英文的字母、中文的漢字。
計算中的底層不管是數據還是指令,都是二進制的0、1,硬件實現為晶體管的開關或電容中電信号的有無。
數的編碼:包括正整數的原碼、負整數的補碼、浮點數的IEEE754碼制(包括階碼的移碼);
字符的編碼:如ASCII碼、utf-8的unicode碼,GBK碼等,用不同長度的二進制位來表示。
位圖的編碼:位圖像素的數字化;
聲明的編碼:聲波振幅、頻率采樣的數字化;
3 數位:組合、硬件實現與位運算前面說了,用晶體管或電容可以實現一個二進制位,或0或1。
一個二進制位包含的信息量稱為一 比特 。
從門電路到集成電路,從簡單到複雜的關鍵在于組合。
後面會提到,計算機中以8個位組合成一個字節,做為基本的尋址單位,但也可以進行位層面的計算,這就是位運算。
4 字節:存儲程序概念與尋址單位計算機中以8個位組合成一個字節,做為基本的尋址和運算單位。不同的數據類型使用長度不同的内存單元(字節數不同)。
在ASCII碼中,用一個字節來表示一個字符。不同的字符集的字符(如寬字符、多字符)需要不同數量的字節數。
5 字長:一次批量訪問和處理的比特位對于現在的電腦來時,其内部實現都是串行操作的(可以利用電腦速度快和人類感官速度慢的特點,通過快速切換的串行來模拟人類表面感覺的并行),單個訪問字節效果太低了,電腦可以一次訪問多個字節,如32機就是一次可以訪問32個位,4個字節,64機一次可以訪問8個字節。
一次性處理事務的一個固定長度的位(bit)的位數叫字長。計算機中大多數寄存器的大小是一個字長。計算機處理的典型數值(如int)也可能是以字長為單位。CPU和内存之間的數據傳送單位也通常是一個字長。還有而内存中用于指明一個存儲位置的地址也經常是以字長為單位的。現代計算機的字長通常為32、64位。内部數據的表示或處理通常是字長的位數或分數。
6 内存對齊考慮到計算機按字長處理數據的特點,對于複合數據類型,如結構體、類、共用體,都會考慮通過内存對齊來提供數據操作(讀寫)效率。每個特定平台上的編譯器都有自己的默認“對齊系數”(也叫對齊模數)。比如32位windows平台下,VC默認是按照8bytes對齊的(VC->Project->settings->c/c ->Code Generation中的truct member alignment 值默認是8),程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊系數”。
内存對齊規則:
1、數據成員對齊規則:結構(struct)(或聯合(union)或類(class))的數據成員,第一個數據成員放在offset為0的地方,以後每個數據成員的對齊按照#pragma pack(n)指定的數值和這個數據成員自身長度中,比較小的那個進行。
成員對齊系數 = min(數據成員類型, n)
2、結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之後,結構(或聯合)本身也要進行對齊(數據成員後的内存單元是否需要填充),對齊将按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
整體對齊系數 = min((max(最大數據成員長度), 8)
3、結合1、2推斷:當#pragma pack的n值等于或超過所有數據成員長度的時候,這個n值的大小将不産生任何效果。
如果沒有通過pragma pack(m) 指定數值,VC規定各成員變量存放的起始地址相對于結構的起始地址的偏移量必須為該變量的類型所占用的字節數的倍數。
下面列出常用類型的對齊方式(vc6.0,32位系統)。
類型 對齊方式(變量存放的起始地址相對于結構的起始地址的偏移量)
char 偏移量必須為sizeof(char)即1的倍數
Short 偏移量必須為sizeof(short)即2的倍數
int 偏移量必須為sizeof(int)即4的倍數
float 偏移量必須為sizeof(float)即4的倍數
double 偏移量必須為sizeof(double)即8的倍數
各成員變量在存放的時候根據在結構中出現的順序依次申請空間,同時按照上面的對齊方式調整位置,空缺的字節VC會自動填充。同時VC為了确保結構的大小為結構的字節邊界數(即該結構中占用最大空間的類型所占用的字節數)的倍數,所以在為最後一個成員變量申請空間後,還會根據需要自動填充空缺的字節,也就是說:結構體的總大小為結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之後加上填充字節(trailing padding)。
例1:
struct Node1{ double m1; char m2; int m3; };
為上面的結構Node1分配空間的時候,VC根據成員變量出現的順序和對齊方式,
1 先為第一個成員m1分配空間,其起始地址跟結構的起始地址相同(剛好偏移量0剛好為sizeof(double)的倍數),該成員變量占用sizeof(double)=8個字節;
2 接下來為第二個成員m2分配空間,這時下一個可以分配的地址對于結構的起始地址的偏移量為8,是sizeof(char)的倍數,所以把m2存放在偏移量為8的地方滿足對齊方式,該成員變量占用 sizeof(char)=1個字節;
3 接下來為第三個成員m3分配空間,這時下一個可以分配的地址對于結構的起始地址的偏移量為9,不是sizeof (int)=4的倍數,為了滿足對齊方式對偏移量的約束問題,VC自動填充3個字節(這三個字節沒有放什麼東西);
4 最後要考慮整體對齊規則,這時下一個可以分配的地址對于結構的起始地址的偏移量為12,剛好是sizeof(int), 由于8 4 4 = 16恰好是結構體中最大空間類型double(8)的倍數,所以sizeof(Node1) =16.
例2:
struct Node2{ char a; int b; char c; };
再來分析一下Node2,
1 成員a占一個字節,所以a放在了第1位的位置;
2 第二個變量b占4個字節,為保證起始位置是4(sizeof(b))的倍數,所以需要在a後面填充3個字節,也就是b放在了從第5位到第8位的位置;
3 然後就是c放在了9的位置,此時4 4 1=9。
4 接下來考慮字節邊界數,9并不是最大空間類型int(4)的倍數,應該取大于9且是4的的最小整數12,所以sizeof(Node2) = 12.
例3:
typedef struct{ char a; char b; int c; }Node3;
同樣的方法我們要計算出sizeof(Node3) = 8;
例4:
struct node4 { char c1; Node3 n3; char c2 };
n3的最寬簡單成員的類型為int,n3在考慮最寬簡單類型成員時是将Node3“打散”看的,所以n3的最寬簡單類型為int,這樣,通過n3定義的變量,其存儲空間首地址需要被4整除,整個sizeof(n3)的值也應該被4整除。
1 c1的偏移量為0;
2 n3的偏移量呢?這時n3是一個整體,它作為結構體變量也滿足前面三個準則,所以其大小為8,偏移量為4,c1與n3之間便需要3個填充字節;
3 而c2與n3之間就不需要了,所以c2的偏移量為12;
4 考慮整體偏移,算上c2的大小為13,13是不能被4整除的,這樣末尾還得補上3個填充字節。最後得到sizeof(S3)的值為16。
通過上面的叙述,我們可以得到一個公式:
結構體的大小等于最後一個成員的偏移量加上其大小再加上末尾的填充字節數目,即:
sizeof( struct ) = offsetof( last item ) sizeof( last item ) sizeof( trailing padding )
再來實一個整體實例:
#include <iostream> using namespace std; //預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊系數”。 #pragma pack(8) typedef struct people{ double weight; char sex; // 8 1 short age; // 9 1 2 int money; // 12 4 char flag; // 16 1 7 }strtSize; //}__attribute__((packed)) strtSize;/讓GCC編譯器取消結構在編譯過程中的優化對齊 void main() { cout<<sizeof(double)<<endl; // 8 cout<<sizeof(strtSize)<<endl; // 24 }
-End-
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!