C ++中循环变量的变量

时间:2016-12-20 16:23:32

标签: c++ 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 ++实现。

3 个答案:

答案 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;
}