2012-02-27 74 views
7

我擔心我可能會丟失一些微不足道的東西,但是如果您希望保留原始的無符號值,似乎沒有實際的安全方式來轉換爲簽名類型/從一個簽名類型。沒有兼容的方式來轉換相同大小的簽名/無符號

在reinterpret_cast上,5.2.10沒有列出整數轉換爲整數,因此沒有定義(並且static_cast沒有定義額外的轉換)。關於積分轉換4.7.3基本上說大的無符號轉換將被實現定義(因此不可移植)。

這似乎是有限制的,因爲我們知道,例如,在任何硬件上,uint64_t應該可以安全地轉換爲int64_t,並且返回時沒有值的變化。再加上關於標準佈局類型的規則實際上保證了安全轉換,如果我們在memcpy這兩種類型之間而不是分配。

我正確嗎?爲什麼在整數類型之間不能有reinterpret_cast足夠大小的合法理由?


澄清:絕對無符號的簽名版本不保證值,但它只是我在考慮往返(無符號=>簽訂=>無符號)


UPDATE:仔細查看答案並交叉檢查標準,我相信memcpy實際上並不能保證工作,因爲它沒有聲明這兩種類型是佈局兼容的,並且都不是char類型。進一步更新,挖掘到C標準這個memcpy應該工作,因爲目標的大小足夠大,它複製字節。


答案:似乎有沒有技術理由的reinterpret_cast是不允許進行這種轉換。對於這些固定大小的整數類型,一個memcpy保證工作,事實上,只要中間可以表示所有的位模式,任何中間類型都可以使用(因爲可能存在陷阱模式,float可能是危險的)。一般來說,你不能在任何標準佈局類型之間使用memcpy,它們必須是兼容的或char類型的。這裏的整數是特殊的,因爲它們有額外的保證。

+0

我認爲你總是可以這樣做,畢竟reinterpret_cast只是告訴編譯器如何解釋一個內存位置而不改變位置值。在任何硬件上「 – 2012-02-27 15:27:01

+0

」。恰恰是這一點。也許在任何你可以使用的硬件上,但C++並非設計用於學院的理由來支持除了2nd補充以外的其他一些東西,而是因爲實際上有這樣的硬件。並且(u)intXX_t類型只需要在計算「as-if」時表現爲「二進制補碼」,但並不要求硬件必須是。 – PlasmaHH 2012-02-27 15:30:59

+0

@PlasmaHH,我的觀點是'uint64_t','int64_t'是確切的大小(如果在硬件上支持),因此'memcpy'保證轉換(通過標準佈局類型的規則)。我不關心2的補碼,我想要一個來回轉換。 – 2012-02-27 15:33:45

回答

2

正如你所指出的,memcpy的是安全的:

uint64_t a = 1ull<<63; 
int64_t b; 
memcpy(&b,&a,sizeof a); 

價值爲b仍實現定義的,因爲C++不需要補碼錶示, 但轉換回給你的原始值。

正如Bo Persson指出int64_t將是二進制補碼。因此,memcpy應該產生一個帶符號的值,其中簡單的整數轉換回無符號類型被很好地定義爲原始的無符號值。

uint64_t c = b; 
assert(a == c); 

此外,您還可以實現自己「signed_cast」,使轉換容易的(我不趁補的事情,因爲這些不限於intN_t類型):

template<typename T> 
typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value,T>::type 
signed_cast(typename std::make_unsigned<T>::type v) { 
    T s; 
    std::memcpy(&s,&v,sizeof v); 
    return s; 
} 

template<typename T> 
typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value,T>::type 
signed_cast(typename std::make_signed<T>::type v) { 
    T s; 
    std::memcpy(&s,&v,sizeof v); 
    return s; 
} 
+0

關於爲什麼'reinterpret_cast'不允許這種轉換的任何見解? – 2012-02-27 15:55:48

+1

如果定義了'int64_t',則它必須是二進制補碼64位整數,不帶填充位。所以如果定義了'int64_t','memcpy'應該可以工作,並且我從來沒有聽說過一個簡單的強制轉換不起作用的情況(儘管它是正式實現的)。對於像int或long這樣的標準整型,由於位數和有符號表示是實現定義的(如果實現不是二進制補碼,可能會發生奇怪的事情),因此不太確定。 – 2012-02-27 16:00:49

+0

@JamesKanze你確定'int64_t'必須是二進制補碼嗎? – 2012-02-27 16:19:59

0

這個問題基本上是一個n位無符號可能而不是有一個表示在n位有符號類型。例如,一個8位無符號數的最大值爲256,而一個8位有符號值的數值不能大於128(並且注意這對於硬件實現是不受限制的:任何表示都需要一位簽名。)

+0

啊,但僅此而已,標準佈局類型_guarantee_的規則,您可以在不損失任何價值的情況下來回轉換。也就是說,在簽名變量中存儲什麼邏輯值並不重要,使用memcpy可以保證您可以返回原始的無符號值(因爲類型都是標準佈局並且具有足夠的大小)。 – 2012-02-27 16:02:07

+3

最大值實際上是255和127.只是說'。 :P – TheBuzzSaw 2012-02-27 16:19:09

-1

只要運行

#include <cstdio> 
#include <stdint.h> 
using namespace std; 

int main() 
{ 
    int64_t a = 5; 
    int64_t aa; 
    uint64_t b; 
    double c; 

    b = *reinterpret_cast<uint64_t *>(&a); 
    aa = *reinterpret_cast<int64_t *>(&b); 

    if (a == aa) { 
     printf("as expected, a == aa\n"); 
    } 

    c = *reinterpret_cast<double *>(&a); 
    aa = *reinterpret_cast<int64_t *>(&c); 

    if (a == aa) { 
     printf("again, as expected, a == aa\n"); 
    } 

    printf("what is this I don't even %f.\n", c); // this one should give some undefined behavior here 

    return 0; 
} 

無法在評論適應它。

+0

這不能保證能夠正常工作,並且可能會出現段錯誤,並且仍然符合標準。 'reinterpret_cast'可用於往返,但不能將中間值用作實際指針,因爲它是實現定義的。 _我不知道任何硬件/編譯器在哪裏不行,但是_ – 2012-02-27 16:35:18

+0

很可能,變量c不包含有效的IEEE 754格式。從那時起,沒有什麼是保證工作的。儘管如此,C++並沒有告訴任何有關整數應該如何打包的內容。 – foxx1337 2012-02-27 16:43:08

+0

此外,這違反了嚴格的別名規則(將一個整數作爲「double」來別名,反之亦然)。 – 2012-02-27 17:06:41

1

想必這不是因爲與符號 - 幅度表示機器會違反了最小驚訝的簽署0映射到無符號0原則,同時簽署的-0將映射到一些其他的(可能非常大)數允許的。

考慮到memcpy解決方案的存在,我認爲標準組織決定不支持這種不直觀的映射,可能是因爲unsigned-> signed-> unsigned不如指針 - >整數 - >指針那麼有用。

+1

然後暫時忽略這些大小的類型。我想說從'intptr_t'到'uintptr_t'的映射將是有用的序列。只要考慮你是否試圖將API結合到恰好選擇不同簽名的API中。 – 2012-02-27 16:47:08

-1

除非我誤解的問題,只是把有符號的類型爲無符號的類型,反之亦然再次回到:

#include <iostream> 

int main() 
{ 
    signed char s = -128; 
    unsigned char u = s; 
    signed char back = u; 

    std::cout << (int)u << std::endl; 
    std::cout << (int)back << std::endl; 

    return 0; 
} 

./a.out 
128 
-128 
+1

問題是關於標準的保證。大家完全期待往返通用硬件,但標準實際上並不能保證它。這是令人驚訝的,因爲如果你使用'memcpy'來完成往返*,*就能保證工作。 – 2012-02-27 16:52:13

+0

啊,是的,我現在看到。對不起,噪音。有趣的問題,我的印象是有保證的。 – 01100110 2012-02-27 16:56:55

3

我們知道,你不能施放的任意比特序列浮點,因爲它可能是一個陷阱代表。

是否有任何規則說在簽名整型中不能有陷印表示? (由於定義範圍的方式,無符號類型不能,因爲有效值需要所有表示)

簽名表示也可以包括等價類(如+0 == -0),並且可以將這樣的類中的值強制爲規範表示,從而打破往返。

下面是來自標準的相關規則(sectin 4.7,[conv.integral]):

如果目標類型是無符號的,所得到的值是至少無符號整數全等到源整數(模2 Ñ其中n是用於表示無符號類型的位數)。 [注:在二進制補碼錶示中,這種轉換是概念性的,並且位模式沒有變化(如果沒有截斷)。 - 結束註釋]

如果目標類型是帶符號的,如果它可以用目標類型(和位域寬度)表示,則該值不變。否則,該值是實現定義的。

如果您的意思是在指針或引用上使用reinterpret_cast而不是值,則必須處理the strict-aliasing rule。而你發現的是this case is expressly allowed

+0

嗯,按照這個邏輯,你也不應該把memcpy加到浮點值上,因爲它可能會陷入陷阱。也許是簽名/無符號整型之間的memcmpy實際上不能保證? – 2012-02-27 17:08:54

+0

@ edA-qamort-ora-y:'memcpy'永遠不會陷阱,因爲它不解釋它的參數。但使用結果值可能是一個問題。 – 2012-02-27 17:11:08

+0

您是否同意通過'memcpy'做一次往返保證保留原始值? – 2012-02-27 17:39:51

相關問題