2016-12-20 197 views
2

我正在從C#遷移到C++,而且我遇到了一個似乎沒有道理的範圍問題。以下是一些顯示我的問題的示例代碼。在C++中循環遍歷的變量

int rounds = 0; 
char *names[10]; 
while (rounds < 10) 
{ 
    char name[10]; 
    if (rounds == 1) 
    std::strcpy(name, "Test"); 

    std::cout << &name << " " << name << std::endl; 

    names[rounds] = name; 

    ++rounds; 
} 

我不明白爲什麼我總是收到相同的地址,以及爲什麼變量在第二輪後已經設置。

解釋當我遇到這個時我在做什麼。我正在嘗試使用名稱鍵和回答值創建一個無序的地圖。我認爲應該發生的事情是char name [10]應該在每次while循環時創建一個新變量。但是相反,我得到完全相同的字符串文字,第二輪將使用第二個名字將第一個名字擦掉。

我明白字符串文字是古老的,這不是C++字符串的問題。我也使用類似的問題解決了:

char *name = new char[10]; 

但我真的很好奇,爲什麼上述代碼每次都在同一個陣列地址結束。是某種編譯器優化,還是我理解範圍錯誤?

編輯:我相信我已經完善了我實際上在尋找的東西。這個例子可能不是我需要回答的問題的最佳選擇。

我在尋找的是創建一個指向循環期間創建的對象的指針的列表(不管是數組還是其他)的正確方法。如果可能的話,C和C++實現將不勝感激。

+1

您還沒有初始化的變量,任何東西 - 所以打印出來是自找麻煩。 – doctorlove

回答

3

本地變量很可能會在每次循環時在堆棧上的相同內存地址上滾動,並且由於您未初始化它,它仍將包含之前存在的任何內容。

正開始將證明這一點:

int rounds = 0; 
char *names[10]; 
while (rounds < 10) 
{ 
    char name[10] = ""; //<--- this here 
    if (rounds == 1) 
     std::strcpy(name, "Test"); 

    std::cout << &name << " " << name << std::endl; 

    names[rounds] = name; 

    ++rounds; 
} 
+0

那麼在我的現實世界的例子中,第一個值在連續循環中被寫入的地方,是每次發生在相同地址上的副作用還是故意保留變量? – HappyHelix

+0

絕對是一個副作用 – doctorlove

2

您使用的是變量名,這是的C/C++中最有用的功能之一了託管語言,因爲它不需要堆分配並且本質上是可重入的。

棧變量的範圍從聲明到下一個關閉塊(}),所以如果你想在循環中使用它,你必須爲每次迭代初始化它。

所以你的代碼有一個問題,你在第二次迭代初始化它,而不是第一次初始化它。

事實上變量的地址總是相同是正確的,因爲對於每次迭代,編譯器都會爲堆棧保留相同的10個字節。 (根據處理器/ OS上的編譯器可保留超過10個字節)

+0

從技術上講,他們在循環的第二次迭代中分配給它。初始化只發生在聲明點。 – NathanOliver

+0

對不起。第一次沒有分配給它,就像是「沒錯,因爲我們沒有觸及它」,完整性檢查。 – HappyHelix

+0

我真的很想了解C和託管語言之間的差異。 – HappyHelix

0
int rounds = 0; 
char *names[10]; 
while (rounds < 10) 
{ 

此時,name不存在。

在這裏你實例化一個新的name與未初始化的內容。這發生在每個循環迭代上。

char name[10]; 

在這裏,您初始化新的緩衝區,如果它是第一輪:

if (rounds == 1) 
    std::strcpy(name, "Test"); 

在這一點上,將緩衝區的內容都在2回合開始初始化。

name的地址是特定於實現的,並不保證是特別的。它在每次循環迭代中碰巧是一樣的,這當然是很好的,但是你不需要注意,因爲C++的語義沒有任何東西可以告訴你期望什麼。

下面的行調用在回合2起,當您嘗試使用name內容未定義行爲:

//       vvvvvvv- undefined behavior 
    std::cout << &name << " " << name << std::endl; 

在這裏,你的name地址存儲:

names[rounds] = name; 

    ++rounds; 

目前的關閉大括號時,name的範圍結束。該物體被破壞並不復存在。上面存儲的name的地址是一個懸掛指針,因爲name實例現在已被銷燬。

這是純粹的巧合,下一次name被上面的例子,它發生在同一地址,你會得到一些可用的數據。它也可以格式化硬盤,所以要小心:未定義的行爲字面意思是代碼可以自由地做任何事情。

} 

在任何情況下,你的代碼都是一些非常奇怪的C和C++範例。當你編寫C++時,你想看到的最後一件事是裸號數組char和使用C字符串API。

如果你用慣用的C++編寫你的代碼,它會和C#中的幾乎一樣。而且奇妙的是,與C版本相比,這段代碼沒有任何開銷,但是所有的安全結構都是你無法從C獲得的(因爲C不是C++!)。

int main() { 
    std::vector<std::string> names; 
    const int N = 10; 
    names.reserve(N); // optional to prevent the vector from reallocating as it grows 
    std::generate_n(std::back_inserter(names), N, +[]{ return "Test"; }); 
    for (auto const & name : names) 
     std::cout << name << std::endl; 
} 

如果你堅持使用C字符串的API編寫的代碼 - 絕對沒有理由,因爲它比C中沒有性能上的優勢++使用得當 - 在這裏你去:

int main() { 
    const int N = 10; 
    char *names[N]; 
    for (int i = 0; i < N; i ++) 
     names[i] = strdup("Test"); 
    for (auto name : names) 
     std::cout << (void*)name << " " << name << std::endl; 
} 

在這裏,每個存儲在names中的地址將會不同,因爲strdup會分配一個新的字符串。輸出:

0x7fce7b700000 Test 
0x7fce7b700010 Test 
0x7fce7b700020 Test 
0x7fce7b700030 Test 
0x7fce7b700040 Test 
0x7fce7b700050 Test 
0x7fce7b700060 Test 
0x7fce7b700070 Test 
0x7fce7b700080 Test 
0x7fce7b700090 Test 

你也可以這樣做:

int main() { 
    char *names[10]; 
    std::generate(std::begin(names), std::end(names), +[]{ return strdup("Test"); }); 
    for (auto name : names) 
     std::cout << (void*)name << " " << name << std::endl; 
} 
+0

很高興知道我只是一樣困惑,因爲我認爲我是XD 我認爲我正在尋找一個關鍵是這樣的: 什麼是正確的C方式來創建一系列元素?循環,做一件事,把它放在一件事情上,最後收集一些東西。這似乎是我現在缺少的實際有用的結果。 – HappyHelix

+0

你問了一個C++問題。如果你想問一個C問題,請這樣做:作爲一個單獨的問題。由於C不是C++的子集,它們是完全不同的編程語言。如果你正在編寫C++,那麼你不應該像在C中那樣思考 - 你的代碼會很糟糕。 –

+0

我正在研究一些比C++更C的非常舊的代碼,所以最好是我知道事情的最基本的骨架實現。 – HappyHelix