0

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

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

我的意思是,这些代码在逻辑上是否极其相同,或者由于其本地性质会有所不同?

4

5 回答 5

1

一个实际的区别是i. 在第一种情况下,i在循环的最后一次迭代之后继续存在。在第二个它没有。

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

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

于 2012-07-11T21:13:23.153 回答
0

它们之间几乎没有区别。

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

由于i不在循环外使用,并且在一次迭代中分配给它的值不会在以后的迭代中使用,因此最好在本地声明它,如第二个示例中所示。

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

于 2012-07-11T21:12:56.700 回答
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)  
    }

如您所见,输出是相同的。请注意,即使在调试版本中,程序集也保持不变。

于 2012-07-11T21:25:20.697 回答
0

Ed S. 表明大多数编译器将为这两种情况生成相同的代码。但是,正如 Mahesh 指出的那样,它们实际上并不相同(即使i在版本 1 中在循环范围之外使用但不是版本 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 个 operator=(IType&) 和 1 个 ~IType(),而第二个是 10 个 IType(IType&) 和 10 个 ~IType()。对于某些课程,这可能是真的。但是,如果您考虑 operator= 的工作原理,它通常必须至少相当于复制构造和破坏。

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

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

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

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

于 2012-07-11T22:42:57.573 回答
-2

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

于 2012-07-11T21:08:01.660 回答