語言支持多種整型數據類型來表示有限范圍的整數。每種類型都能用關鍵字來指定大小,這些關鍵字包括char、short、long,同時還可以指示被表示的數字是非負數(聲明為unsigned)、或者可能是負數(默認)。另外,為這些不同的大小分配的字節數根據程序編譯為 32 位還是 64 位而有所不同。
long的大小與int一樣大,屬于歷史遺留問題,歷史上的int在16位平臺時代是2個字節,long是4個字節,所以long要long,等到32位平臺將int提升到32位一個字長時,int和long的長度就一樣。
不同word size對應的最值的16進制編碼(其中的U表示Unsigned,T表示Two's comlement(補碼)):
注意上面的16進制與二進制的對應關系:
7:0111
8:1000
F:1111
可以看出有以下數量關系:
假設有一個整數數據類型,有位(word size or width)。將其寫成向量,將其視為二進制表示的無符號數,則每一位取值范圍為0或1。
用函數(Binary to Unsigned的縮寫,長度為)表示為:
函數將一個長度為w 的0、1 串映射到非負整數。
例:
可以用長度為的指向右側箭頭的條表示每個位的位置 i。每個位向量對應的數值就等于所有值為 1 的位對應的條的長度之和。
w位二進制所能表示的無符號整數的范圍:
目前,最常見的計算機有符號整型數的計算機表示方式為補碼形式。在補碼中,將字的最高有效位解釋為負權(negative weight)。這里通過函數 B2T_w(Binary to Two's-complement的縮寫)來表示:
例:
最高有效位也稱為符號位,它的“權重”為,是無符號表示中權重的負數。符號位被設置為1 時,表示值為負,而當設置為0 時,值為非負。
補碼 + 它的負數 = 模;
補碼和它的負數的二進制位只有最低位是相同的,其它位都是相補:
補碼 && 它的負數=1;
這也是經常有以下表述的來由:
對于負數,補碼等于反碼+1。
補碼使用最高位表示符號,但與無符號整數編碼表示的總體值域的個數是一致的:。
我們用向左指的條表示符號位具有負權重。于是,與一個位向量相關聯的數值是由可能的向左指的條和向右指的條加起來決定的。
最高位以外的其它位相對于最高位而言,是一個此漲彼消的狀態。
w位二進制所能表示的有符號整數的范圍:
負整數的范圍比正整數的范圍大1,多出來的數就是100…000(二進制序列,最小的負數),該數取反加1等于模,截斷后就是0了。
C 語言允許在各種不同的數字數據類型之間做強制類型轉換。遵循底層的位表示不變,而按不同類型的編碼規則進行解釋的原則。
如果是相關類型的隱式轉換,C語言設置了一系列的轉換規則。
如果是不相關類型的強制轉換,C語言會在位級層面按編碼規則做重新解釋(解碼)。
例如,假設變量x 聲明為int,u 聲明為unsigned,表達式(unsigned)x 會將x 的值轉換成一個無符號數值,而(int)u 將u 的值轉換成一個有符號整數。將有符號數強制類型轉換成無符號數。另外,一個表達式中,如果同時存在無符號整數與有符號整數,計算時,有符號整數會隱式轉換為無符號整數。
對于有符號整數的正整數部分,與無符號整數是一致的。
對于對于有符號整數的負整數部分,與無符號整數只有最高位的解釋不同,其它位是一致的。
所以有以下數量關系:
負整數x→U = x+
u→負整數x=U -
由于C 語言對同時包含有符號和無符號整型數表達式的隱式轉換,會出現一些奇特的行為。當執行一個運算時,如果它的一個運算數是有符號的而另一個是無符號的,那么C語言會隱式地將有符號整數強制類型轉換為無符號數,并假設這兩個數都是非負的,來執行這個運算。就像我們將要看到的,這種方法對于標準的算術運算來說并無多大差異,但是對于像<和>這樣的關系運算符來說,它會導致非直觀的結果。
有符號數到無符號數的隱式強制類型轉換導致了某些非直觀的行為。而這些非直觀的特性經常導致程序錯誤,并且這種包含隱式強制類型轉換的細微差別的錯誤很難被發現。
一個常見的運算是在不同字長的整數之間轉換,同時又保持數值不變。
對于有符號整數和無符號整數,兩者在擴展時,高位的符號擴展有所區別:
short sx=val; /* -12345 */
unsigned short usx=sx; /* 53191 */
int x=sx; /* -12345 */
unsigned ux=usx; /* 53191 */
在采用補碼表示的32 位大端法機器上的內存布局:
有符號整型使用1來做符號擴展。
無符號整型使用0來做符號擴展。
有符號整數在做右移運算時,也會存在符號擴展的問題。
假設我們不用額外的位來擴展一個數值,而是減少表示一個數字的位數。例如下面代碼中這種情況:
int x=53191;
short sx=(short) x; /* -12345 */
int y=sx; /* -12345 */
當我們把x 強制類型轉換為short 時,我們就將 32 位的int 截斷為了 16 位的short int。
當將一個w位的整型數截斷為一個k位整型數字時,我們會丟棄高w-k位。
無符號整數取模:
有符號整數的補碼取模后要做轉換 :
C語言的整型數字是一個有限字長的表示,當在計算時存在溢出時,會做模運算處理。
當兩個w位的無符號整型相加時,其結果可能是一個w+1位的無符號數。
當兩個w位的無符號整型相加時其和等于或超過時,就會發生溢出,其結果為和與的模運算:
補碼加法存在正溢出與負溢出:
首先要明白,一個正數和一個負數相加,結果一定不會溢出(因為結果的絕對值一定小于兩個加數的絕對值,兩個加數都表達出來了,結果一定能表達出來。)
所以,發生溢出的情況一定是符號相同的兩個數相加。
分情況討論:
① 正整數pa + 正整數pb=r
符號位0,數位相加,如果結果的符號位變成1了,那一定是兩個加數的最高位相加進上來的。
假設將一個長度 w=4 的0、1 串映射到有符號整數,其最大的正數只能是0111,也就是7。
0111+0001=1000 // 7 + 1 = 8-2^4=-8,向下溢出
0111+0011=1010 // 7+ 3=10-2^4=-6,向下溢出
發生向下溢出,判斷的方式之一是:r<pa || r<pb。
② 負整數na + 負整數nb=r
符號位都是1,所以符號位一定會進位。數位相加,如果最后符號位是0,說明結果變成正的了,那一定是發生溢出了(負+負!=正)。
假設將一個長度 w=4 的0、1 串映射到有符號整數,其最小的負數只能是1000,也就是-8。
1000+1111=11000 ->0110 // -8+(-1)=-9+2^4=7,向上溢出
1011+1011=10110 ->0110 // -5+(-5)=-10+2^4=6,向上溢出
另外一種情況,沒有改變符號位的溢出,屬正常溢出,正如有符號位擴展、算術移位一樣:
1100+1100=10000 ->0000 // -4+(-4)=-8, 正常溢出
1111+1111=11110 ->1110 // -1+(-1)=-2, 正常溢出
發生向上溢出,判斷的方式之一是:r<pa || r<pb。
CPU的標志寄存器有一個溢出標志位,反映有符號數加減運算是否溢出。如果運算結果超過了8位或者16位有符號數的表示范圍,則OF置1,否則置0。
① 兩整數乘除的結果還是一個整數類型(類型一致),如果是除法,可能存在舍入(舍入到零,向上舍入,向下舍入)的情況;
② 兩整數乘法要考慮溢出的情況。
③ 位運算也是一種特殊的整數乘除,乘數與除數是2的不同的整數冪(當一個整數乘除一個常數時,這個常數如果不是某個整數次冪,可以拆解成不同的冪次的加減)。
8.1 帶符號數產生意外結果的例子。這個例子會造成無限循環,因為sizeof會返回unsigned int 類型,由此帶來的結果是,i - sizeof(char)這個表達式的求值結果將會是 unsigned int (隱式轉換 !!),i 會隱式轉換為unsigned,i從 0 減 1 后變成-1,其二進制編碼是 0xFFFFFFFF,轉換成無符號數是2^32-1,從而產生無限循環,有時候你需要特別留心這種不經意的錯誤 !
int n=10, i;
for (i=n - 1 ; i - sizeof(char) >=0; i--)
printf("i: 0x%x\n",i);
if (-1 > 0U) // -1的二進制編碼是0xFFFFFFFF,轉變成無符號數是2^32-1
printf("You Surprised me!\n");
8.2 以下是2002年的freeBSD內核的部分代碼,其中包含了漏洞,假設惡意人員將負值作為maxlen傳入這個函數,有發生什么情況?
以下size_t的類型是typedef unsigned int size_t的類型定義:
#define KSIZE 1024
char kbuf[KSIZE];
/* Copy at most maxlen bytes from kernel region to user buffer */
int copy_from_kernel(void *user_dest, int maxlen) {
int len=KSIZE < maxlen ? KSIZE : maxlen;
memcpy(user_dest, kbuf, len);
return len;
}
/* Declaration of library function memcpy */
void *memcpy(void *dest, void *src, size_t n);
/* Malicious Usage */
void getstuff() {
char mybuf[MSIZE];
copy_from_kernel(mybuf, -512); // -512轉變成無符號數后是2^31+512
}
8.3 給定一個有序的整型數組,請編程實現二分查找算法。
高德納在《計算機程序設計的藝術》指出,雖然早在1946年就有人將二分查找的方法公諸于世,但直到1962年才有人寫出沒有bug的二分查找程序,可見,寫一個安全的代碼并不容易,你是不是一不小心就寫出像下面這樣的二分查找代碼了?
int binary_search(int a[], int len, int key){
int low=0;
int high=len - 1;
while ( low <=high ) {
int mid=(low + high)/2; // 提示:這里有溢出Bug!
if (a[mid]==key) {
return mid;
}
if (key < a[mid]) {
high=mid - 1;
}else{
low=mid + 1;
}
}
return -1;
}
ref
Randal E. Bryant, David R. O’Hallaron《Computer Systems:A Programmers Perspective》
-End-
:這個符號叫“與”,表示左右兩邊的操作數都為“真”時才為“真”,口訣是真真為真,真假為假,假真為假,假假為假;
||:這個符號叫“或”,表示左右兩邊的操作數只要有一個為“真”時就為“真”,口訣是真真為真,真假為真,假真為真,假假為假;
^:這個符號叫“非”,表示將右邊的操作數從“真”變成“假(非真)”的結果,從“假”變成“真(非假)”的結果。
特性
PHP的特性包括:
PHP 獨特的語法混合了 C、Java、Perl 以及 PHP [8]自創新的語法。
PHP安裝它可以比 CGI或者Perl更快速的執行動態網頁。用PHP做出的動態頁面與其他的編程語言相比,
PHP是將程序嵌入到HTML文檔中去執行,執行效率比完全生成htmL標記的CGI要高許多;
PHP具有非常強大的功能,所有的CGI的功能PHP都能實現,
而且支持幾乎所有流行的數據庫以及操作系統。最重要的是PHP可以用C、C++進行程序的擴展!
這三個運算符不是同一種運算符,&和^屬于位運算符,||屬于邏輯運算符,|屬于位運算符,&即按位與 ,把$a和$b中都為1的位設為1,否則為0,用于二進制的位運算。邏輯運算符||,同為假才是假,否則為真。^相同則為0,不同則為1,同樣用于二進制的位運算
在PHP中,&&與and都表示邏輯與,||與or都表示邏輯或,并且它們都是短路運算符。而它們的區別在于運算的優先級不同。因為運算符and、=、&&的優先級為&&>=>and。運算符or、=、||的優先級為||>=>or。
多人在電腦省略號都是直接按六次句號鍵來濫竽充數,其實這樣打出來的就是六個句號,而不是真正意義上的省略號,非正式場合可能能蒙混過關,一旦到了正規的節骨眼,這樣會影響到切身利益的,比如說考試答題,也許你答題的內容正確,可最后把省略號打成6個句號就完全改變了主觀意識,扣分是毋庸置疑的,還比如做自媒體的 ,這樣也有可能影響到自身賬號的權重
所以早學會正確打省略號很有必要!
我的切身體會提醒您:不要等到想拉屎了才挖茅坑喲……
今天我發中視頻寫標題的時候想打省略號,才發現電腦鍵盤上根本就找不到這個符號,用句號敷衍又視頻怕流量受損,不得已只能停下來,各處查詢,各種實驗……,費了好大勁,最后找到了一個最容易,還是借助鍵盤來完成的方法,為避免您再走我走過的彎路,現把詳細流程奉獻出來:
首先把電腦的輸入模式調至中文模式。
然后同時按住左鍵盤的Shift鍵和數字6鍵
工整的省略號鍵就出來了。
搞懂了,就如此簡單!
錯我已經試過了,分享是希望你不用再去試錯,記得收藏備用!