簡體   English   中英

C中的符號擴展名

[英]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個問題:

  1. 什么是符號擴展名? 是的,我閱讀了Wiki,但不了解何時進行類型提升,符號擴展是怎么回事?
  2. 為什么用ffff .. 64位(指地址addr)?
  3. 當我鍵入強制轉換時,為什么沒有符號擴展名?

編輯:4.為什么不是32位系統中的問題?

<<運算符的左操作數會進行標准提升,因此在您的情況下,它會提升為int -至此為止。 接下來,將值0x4000int乘以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),但具有不同的無符號值。 前者稱為零擴展(適用於無符號數字),后者稱為符號擴展(適用於有符號數字)。 之所以稱為“符號擴展”,是因為“符號位”(最高有效位或最左邊的位)被擴展或復制以使數字更寬。

我花了一段時間和大量的閱讀/測試。
也許我的初學者了解正在發生的事情會幫助您(如我所知)

  1. a.base = 0x40000(1(0)x18)-> 19位位域
  2. ADDR = a.base << 13。
    • a.base可以容納的任何值int也可以容納,因此從19位無符號int位字段轉換為32位有符號整數。 (a.base現在是(0)x13,1,(0)x18)。
    • 現在(轉換為帶符號的整數a.base)<< 13,結果為1(0)x31)。 請記住,它現在是int簽名的。
    • ADDR =(1(0)X31)。 addr是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 int32-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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM