[英]sign extension in C
我正在這里看以了解符號擴展名: http : //www.shrubbery.net/solaris9ab/SUNWdev/SOL64TRANS/p8.html
struct foo {
unsigned int base:19, rehash:13;
};
main(int argc, char *argv[])
{
struct foo a;
unsigned long addr;
a.base = 0x40000;
addr = a.base << 13; /* Sign extension here! */
printf("addr 0x%lx\n", addr);
addr = (unsigned int)(a.base << 13); /* No sign extension here! */
printf("addr 0x%lx\n", addr);
}
他們聲稱:
------------------ 64位:
% cc -o test64 -xarch=v9 test.c
% ./test64
addr 0xffffffff80000000
addr 0x80000000
%
------------------ 32位:
% cc -o test32 test.c
% ./test32
addr 0x80000000
addr 0x80000000
%
我有3個問題:
編輯:4.為什么不是32位系統中的問題?
<<
運算符的左操作數會進行標准提升,因此在您的情況下,它會提升為int
-至此為止。 接下來,將值0x4000
的int
乘以2 13 ,這將導致溢出,從而導致不確定的行為。 但是,我們可以看到發生了什么:現在,表達式的值就是INT_MIN
,最小的可表示int
。 最后,當您將其轉換為無符號的64位整數時,通常的模塊化算術規則要求結果值為0xffffffff80000000
。 同樣,轉換為無符號的32位整數將得到值0x80000000
。
要對無符號值執行操作,您需要使用強制轉換來控制轉換:
(unsigned int)(a.base) << 13
a.base << 13
按位運算符在其兩個操作數上執行整數提升。
因此,這等效於:
(int) a.base << 13
這是int
類型的負值。
然后:
addr = (int) a.base << 13;
將此帶符號的負值( (int) a.base << 13
)轉換為通過整數轉換unsigned long
構成的addr
類型。
整數轉換(C99,6.3.1.3p2)規則與執行相同:
addr = (long) ((int) a.base << 13);
因為((int) a.base << 13)
是負號,所以轉換long
在此處執行符號擴展。
在另一種情況下,使用演員表時,您具有的等同於:
addr = (unsigned long) (unsigned int) ((int) a.base << 13);
因此在第二種情況下不會執行符號擴展,因為(unsigned int) ((int) a.base << 13)
是一個無符號(當然是正值)的值。
編輯 :正如KerrekSB在他的答案中提到的a.base << 13
實際上不是int
表示的(我假設是32位int
),因此此表達式調用未定義的行為,並且實現有權以任何其他方式進行行為,例如崩潰。
對於信息,這絕對不是可移植的,但是如果您使用gcc
,則gcc
不會將a.base << 13
視為未定義行為。 從gcc
文檔中:
“ GCC不會僅將C99中給出的緯度用於將帶符號的<<<”的某些方面視為未定義,但這可能會發生變化。”
在http://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html中
這更多是關於位域的問題。 請注意,如果將結構更改為
struct foo {
unsigned int base, rehash;
};
您會得到截然不同的結果。
正如@JensGustedt在無符號位域的類型:int或unsigned int中指出的,規范說:
如果一個int可以表示原始類型的所有值(受位字段的寬度限制),則該值將轉換為int;
即使您已指定base為無符號,當您讀取它時,編譯器會將其轉換為帶signed int
。 這就是為什么在將其轉換為unsigned int
時沒有獲得符號擴展的原因。
符號擴展與如何用二進制表示負數有關。 最常見的方案是2s補碼。 在此方案中,-1以32位表示為0xFFFFFFFF,-2是0xFFFFFFFE,依此類推。例如,當我們要將32位數字轉換為64位數字時應該怎么做? 如果將0xFFFFFFFF轉換為0x00000000FFFFFFFF,則數字將具有相同的無符號值(約40億),但具有不同的有符號值(-1與40億)。 另一方面,如果將0xFFFFFFFF轉換為0xFFFFFFFFFFFFFFFF,則數字將具有相同的有符號值(-1),但具有不同的無符號值。 前者稱為零擴展(適用於無符號數字),后者稱為符號擴展(適用於有符號數字)。 之所以稱為“符號擴展”,是因為“符號位”(最高有效位或最左邊的位)被擴展或復制以使數字更寬。
我花了一段時間和大量的閱讀/測試。
也許我的初學者了解正在發生的事情會幫助您(如我所知)
unsigned long
類型(64位),因此將分配的righ值轉換為long int。 從signed int轉換為long int使addr(1)x33,(0)x31。 在您甚至不知道的所有對話之后,這就是要打印的內容: 0xffffffff80000000
。
第二行打印0x80000000
的原因是因為在轉換為long int
之前將其long int
轉換為(unsigned int)。 將unsigned int
轉換為long int
,沒有位符號,因此值僅以尾隨0填充以匹配大小,僅此而已。
與32位的不同之處在於,在從32-bit signed int
到32-bit unsigned long
32-bit signed int
轉換過程中, 32-bit unsigned long
它們的大小匹配並添加尾隨符號,所以: 1(0)x31
將保持1(0)x31
即使從int轉換為long int之后(它們具有相同的大小,其值也會被解釋為不同,但位是完整的。)
鏈接中的報價:
做出此假設的任何代碼都必須更改為同時適用於ILP32和LP64。 雖然在ILP32數據模型中int和long均為32位,但在LP64數據模型中,long為64位。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.