C ++变量声明re:内存使用情况

时间:2012-07-11 21:06:18

标签: c++ pointers

嗨,我知道这是一个非常愚蠢/基本的问题,但代码之间有什么区别: -

int *i;
for(j=0;j<10;j++)
  {
    i = static_cast<int *>(getNthCon(j));
    i->xyz
  }

,有些事情是这样的: -

for(j=0;j<10;j++)
  {
    int *i = static_cast<int *>(getNthCon(j));
    i->xyz;
  }

我的意思是,这些代码在逻辑上是否完全相同,或者由于其本地特性会有什么不同?

5 个答案:

答案 0 :(得分:1)

一个实际差异是i的范围。在第一种情况下,i在循环的最后一次迭代后继续存在。在第二个它没有。

在某些情况下,您可能希望在完成所有计算后知道i的值。在这种情况下,请使用第二种模式。

不太实际的区别在于每种情况下=令牌的性质。在第一个示例中,i = ...表示分配。在第二个示例中,int *i = ...表示初始化。某些类型(但不是int*fp_ContainerObject*)可能会以不同方式处理分配和初始化。

答案 1 :(得分:0)

他们之间差别很小。

在第一个代码示例中,i在循环外声明,因此您在每次迭代时重复使用相同的指针变量。在第二个中,i是循环体的局部。

由于i未在循环外使用,并且在未来的迭代中未使用在一次迭代中分配给它的值,因此在本地声明它是更好的样式,如第二个示例中那样。

顺便说一下,i是指针变量的坏名称;它通常用于int变量,尤其是for循环中使用的变量。

答案 2 :(得分:0)

对于任何理智的优化编译器,内存分配方面没有区别。唯一的区别是i的范围。这是一个示例程序(是的,我知道这里有泄漏):

#include <iostream>

int *get_some_data(int value) {
    return new int(value);
}

int main(int argc, char *argv[]){
    int *p;
    for(int i = 0; i < 10; ++i) {
        p = get_some_data(i);
        std::cout << *p;
    }

    return 0;
}

生成的程序集输出:

int main(int argc, char *argv[]){   
01091000  push        esi  
01091001  push        edi  
    int *p;
    for(int i = 0; i < 10; ++i) {
01091002  mov         edi,dword ptr [__imp_operator new (10920A8h)]  
01091008  xor         esi,esi  
0109100A  lea         ebx,[ebx]  
        p = get_some_data(i);
01091010  push        4  
01091012  call        edi  
01091014  add         esp,4  
01091017  test        eax,eax  
01091019  je          main+1Fh (109101Fh)  
0109101B  mov         dword ptr [eax],esi  
0109101D  jmp         main+21h (1091021h)  
0109101F  xor         eax,eax  
        std::cout << *p;
01091021  mov         eax,dword ptr [eax]  
01091023  mov         ecx,dword ptr [__imp_std::cout (1092048h)]  
01091029  push        eax  
0109102A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1092044h)]  
01091030  inc         esi  
01091031  cmp         esi,0Ah  
01091034  jl          main+10h (1091010h)  
    }

现在,在循环内部声明了指针:

int main(int argc, char *argv[]){   
008D1000  push        esi  
008D1001  push        edi  
    for(int i = 0; i < 10; ++i) {
008D1002  mov         edi,dword ptr [__imp_operator new (8D20A8h)]  
008D1008  xor         esi,esi  
008D100A  lea         ebx,[ebx]  
        int *p = get_some_data(i);
008D1010  push        4  
008D1012  call        edi  
008D1014  add         esp,4  
008D1017  test        eax,eax  
008D1019  je          main+1Fh (8D101Fh)  
008D101B  mov         dword ptr [eax],esi  
008D101D  jmp         main+21h (8D1021h)  
008D101F  xor         eax,eax  
        std::cout << *p;
008D1021  mov         eax,dword ptr [eax]  
008D1023  mov         ecx,dword ptr [__imp_std::cout (8D2048h)]  
008D1029  push        eax  
008D102A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (8D2044h)]  
008D1030  inc         esi  
008D1031  cmp         esi,0Ah  
008D1034  jl          main+10h (8D1010h)  
    }

如您所见,输出相同。请注意,即使在 debug 构建中,程序集仍保持相同。

答案 3 :(得分:0)

Ed S.表明,大多数编译器都会为这两种情况生成相同的代码。但是,正如Mahesh指出的那样,它们实际上并不完全相同(甚至超出了明显的事实,即在版本1中的循环范围之外使用i而不是版本2)。让我试着以一种没有误导性的方式解释这两者是如何成真的。

首先,i的存储空间来自哪里?

标准对此保持沉默 - 只要存储在i范围的整个生命周期内可用,它就可以在编译器喜欢的任何地方。但是处理局部变量的典型方法(技术上,具有自动存储持续时间的变量)是将适当范围的堆栈帧扩展sizeof(i)个字节,并将i存储为该堆栈帧的偏移量。

“教学编译器”可能总是为每个范围创建堆栈帧。但是真正的编译器通常不会打扰,特别是在进入和退出循环范围时没有任何反应。 (除了通过查看程序集或使用调试器进行攻击,你无法区分它们,因此当然可以这样做。)因此,两个版本最终可能会以i为参考与函数堆栈帧完全相同的偏移量。 (实际上,它很合理i将最终出现在注册表中,但这并不会改变任何重要的内容。)

现在让我们来看看生命周期。

在第一种情况下,编译器必须默认初始化i,它在函数作用域中声明,每次通过循环复制赋值,并在函数作用域的末尾销毁它。在第二种情况下,编译器必须在每个循环开始时复制初始化i,并在每个循环结束时销毁它。像这样:

如果i属于班级类型,这将是一个非常显着的差异。 (如果不明白为什么,请参见下文。)但不是,它是一个指针。这意味着默认初始化和销毁​​都是无操作,复制初始化和复制分配是相同的。

因此,生命周期管理代码在两种情况下都是相同的:每次循环都会复制一次,就是这样。

换句话说,允许存储,并且可能是相同的存储;生命周期管理必须是相同的。

我答应回到为什么如果我是班级类型,这些会有所不同。

比较这个伪代码:

i.IType();
for(j=0;j<10;j++) {
  i.operator=(static_cast<IType>(getNthCon(j));
}
i.~IType();

到此:

for(j=0;j<10;j++) {
  i.IType(static_cast<IType>(getNthCon(j));
  i.~IType();
}

乍一看,第一个版本看起来“更好”,因为它是1个IType(),10个运算符=(IType&amp;)和1~IType(),而第二个版本是10个IType(IType&amp;)和10个〜 ITYPE()。对于某些课程,这可能是真的。但是如果你考虑operator =如何工作,它通常必须至少相当于复制结构和破坏。

因此,真正的区别在于第一个版本需要默认构造函数和复制赋值运算符,而第二个版本不需要。如果你拿出那个static_cast位(所以我们讨论的是转换构造函数和赋值而不是复制),你所看到的就等同于:

for(j=0;j<10;j++) {
  std::ifstream i(filenames[j]);
}

显然,在这种情况下,你会尝试将我拉出循环。

但同样,这只适用于“大多数”课程;你可以轻松地设计一个版本2非常糟糕的类,而版本1更有意义。

答案 4 :(得分:-2)

对于每次迭代,在第二种情况下,在堆栈上创建新的指针变量。在第一种情况下,指针变量只创建一次(即,在进入循环之前)