2013-05-21 30 views
0

我試圖向工作夥伴證明,如果真的想要(並知道如何)通過使用一些欺騙手段來改變常量限定變量的值,在演示期間,我發現存在兩種不變價值的「味道」:你不能改變你做的任何事情,以及那些你可以通過使用骯髒的技巧來改變的東西。更改數據時UB的說明

常數值不變時,編譯器使用文本值,而不是存儲在堆棧(readed here)上的價值,這裏是一個piece of code,顯示我的意思:

// TEST 1 
#define LOG(index, cv, ncv) std::cout \ 
    << std::dec << index << ".- Address = " \ 
    << std::hex << &cv << "\tValue = " << cv << '\n' \ 
    << std::dec << index << ".- Address = " \ 
    << std::hex << &ncv << "\tValue = " << ncv << '\n' 

const unsigned int const_value = 0xcafe01e; 

// Try with no-const reference 
unsigned int &no_const_ref = const_cast<unsigned int &>(const_value); 
no_const_ref = 0xfabada; 
LOG(1, const_value, no_const_ref); 

// Try with no-const pointer 
unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value); 
*no_const_ptr = 0xb0bada; 
LOG(2, const_value, (*no_const_ptr)); 

// Try with c-style cast 
no_const_ptr = (unsigned int *)&const_value; 
*no_const_ptr = 0xdeda1; 
LOG(3, const_value, (*no_const_ptr)); 

// Try with memcpy 
unsigned int brute_force = 0xba51c; 
std::memcpy(no_const_ptr, &brute_force, sizeof(const_value)); 
LOG(4, const_value, (*no_const_ptr)); 

// Try with union 
union bad_idea 
{ 
    const unsigned int *const_ptr; 
    unsigned int *no_const_ptr; 
} u; 

u.const_ptr = &const_value; 
*u.no_const_ptr = 0xbeb1da; 
LOG(5, const_value, (*u.no_const_ptr)); 

這將產生下面的輸出:

1.- Address = 0xbfffbe2c Value = cafe01e 
1.- Address = 0xbfffbe2c Value = fabada 
2.- Address = 0xbfffbe2c Value = cafe01e 
2.- Address = 0xbfffbe2c Value = b0bada 
3.- Address = 0xbfffbe2c Value = cafe01e 
3.- Address = 0xbfffbe2c Value = deda1 
4.- Address = 0xbfffbe2c Value = cafe01e 
4.- Address = 0xbfffbe2c Value = ba51c 
5.- Address = 0xbfffbe2c Value = cafe01e 
5.- Address = 0xbfffbe2c Value = beb1da 

由於我在UB林依託(改變const的數據的值)預計該程序的行爲怪異;但這種古怪比我期待的要多。

讓我們supose編譯器使用文字值,然後,當代碼到達指令改變的值的常數(由參考,指針或memcpy荷蘭國際集團),簡單地忽略,只要該值是爲了一個文字(雖然是未定義的行爲)。這解釋了爲什麼值保持不變,但:

  • 爲什麼兩個變量中的相同的內存地址,但包含的值不同?

AFAIK相同的內存地址不能指向不同的值,因此,在輸出中的一個是在撒謊:

  • 什麼真的發生了?哪個內存地址是假的(如果有的話)?

製作上的代碼進行一些更改上面我們可以儘量避免使用文字值,所以掛羊頭賣狗肉會做工作(source here):

// TEST 2 
// Try with no-const reference 
void change_with_no_const_ref(const unsigned int &const_value) 
{ 
    unsigned int &no_const_ref = const_cast<unsigned int &>(const_value); 
    no_const_ref = 0xfabada; 
    LOG(1, const_value, no_const_ref);  
} 

// Try with no-const pointer 
void change_with_no_const_ptr(const unsigned int &const_value) 
{ 
    unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value); 
    *no_const_ptr = 0xb0bada; 
    LOG(2, const_value, (*no_const_ptr)); 
} 

// Try with c-style cast 
void change_with_cstyle_cast(const unsigned int &const_value) 
{ 
    unsigned int *no_const_ptr = (unsigned int *)&const_value; 
    *no_const_ptr = 0xdeda1; 
    LOG(3, const_value, (*no_const_ptr)); 
} 

// Try with memcpy 
void change_with_memcpy(const unsigned int &const_value) 
{ 
    unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value); 
    unsigned int brute_force = 0xba51c; 
    std::memcpy(no_const_ptr, &brute_force, sizeof(const_value)); 
    LOG(4, const_value, (*no_const_ptr)); 
} 

void change_with_union(const unsigned int &const_value) 
{ 
    // Try with union 
    union bad_idea 
    { 
     const unsigned int *const_ptr; 
     unsigned int *no_const_ptr; 
    } u; 

    u.const_ptr = &const_value; 
    *u.no_const_ptr = 0xbeb1da; 
    LOG(5, const_value, (*u.no_const_ptr)); 
} 

int main(int argc, char **argv) 
{ 
    unsigned int value = 0xcafe01e; 
    change_with_no_const_ref(value); 
    change_with_no_const_ptr(value); 
    change_with_cstyle_cast(value); 
    change_with_memcpy(value); 
    change_with_union(value); 

    return 0; 
} 

將會產生以下的輸出:

1.- Address = 0xbff0f5dc Value = fabada 
1.- Address = 0xbff0f5dc Value = fabada 
2.- Address = 0xbff0f5dc Value = b0bada 
2.- Address = 0xbff0f5dc Value = b0bada 
3.- Address = 0xbff0f5dc Value = deda1 
3.- Address = 0xbff0f5dc Value = deda1 
4.- Address = 0xbff0f5dc Value = ba51c 
4.- Address = 0xbff0f5dc Value = ba51c 
5.- Address = 0xbff0f5dc Value = beb1da 
5.- Address = 0xbff0f5dc Value = beb1da 

正如我們所看到的,const限定的變量改爲每個change_with_*通話,並且行爲是一樣的,除了這個事實面前,所以我很想假設怪異BEH當常量數據用作文字而不是數值時,內存地址的內容會顯示出來。

所以,爲了保證這一假設,我做了最後一次測試,改變mainunsigned int valueconst unsigned int value

// TEST 3 
const unsigned int value = 0xcafe01e; 
change_with_no_const_ref(value); 
change_with_no_const_ptr(value); 
change_with_cstyle_cast(value); 
change_with_memcpy(value); 
change_with_union(value); 

令人驚訝的是輸出是一樣的TEST 2code here),所以我假設數據作爲變量傳遞,而不是字面值,因爲它用作參數,所以這讓我想知道:

  • 什麼事情使編譯器決定優化一個常量值作爲文字值?

總之,我的問題是:

  • TEST 1
    • 爲什麼常量值和非常量值共享相同的存儲器地址,但其包含的值是不同的?
    • 產生此輸出的程序遵循哪些步驟?哪個內存地址是假的(如果有的話)?
  • TEST 3
    • 什麼東西讓編譯器來決定優化一個常量的值作爲文本值?
+0

「UB的解釋」 - 這不是一個矛盾嗎? – 2013-05-21 11:22:43

+2

試圖與UB的理由是完全無用的。該標準提供絕對沒有保證,所以所有投注都關閉,*有龍*等。 – syam

+0

因爲獨角獸總是隱藏在明顯的景象。不管你做什麼,都不要吃橘子! – molbdnilo

回答

2

一般情況下,是沒有意義的分析未定義行爲,因爲不能保證您可以將分析結果傳送到不同的程序。

在這種情況下,行爲可以通過假設編譯器已應用稱爲constant propagation的優化技術來解釋。在該技術中,如果使用編譯器知道該值的const變量的值,那麼編譯器會將const變量的使用替換爲該變量的值(因爲它在編譯時已知)。變量的其他用途,如取其地址,不會被替換。

這種優化是有效的,正是因爲改變這種被定義爲const結果不確定的行爲和編譯器的變量允許假設某個程序調用未定義的行爲。

因此,在TEST 1中,地址是相同的,因爲它們都是相同的變量,但是這些值是不同的,因爲每一對中的第一對反映了編譯器設想的(正確的)是變量的值和第二反映了實際存儲在那裏的內容。 在TEST 2TEST 3中,編譯器無法進行優化,因爲編譯器不能100%確定函數參數將引用常量值(並且在TEST 2中,它不會)。