我正在从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 ++实现。
答案 0 :(得分: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;
}
答案 1 :(得分:2)
您正在使用堆栈变量name,这是C / C ++相对于托管语言最有用的功能之一,因为它不需要堆分配,并且本质上是可重入的。
堆栈变量具有从声明到下一个闭包块(})的作用域,因此如果要在循环中使用它,则必须为每次迭代初始化它。
所以你的代码有问题,你在第二次迭代而不是在第一次迭代时初始化它。
变量的地址始终是相同的这一事实是正确的,因为对于每次迭代,编译器都会为堆栈保留相同的10个字节。 (取决于处理器/ OS,编译器可能保留超过10个字节)
答案 2 :(得分: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 ++的语义中没有任何内容可以告诉你会发生什么。
当您尝试使用name
的内容时,下面的行会在第2轮中调用未定义的行为:
// 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;
}