在函数顶部或单独的范围内声明变量?

时间:2010-09-22 20:33:40

标签: c++ c function stack

哪个是优选的,方法1或方法2?

方法1:

LRESULT CALLBACK wpMainWindow(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
        case WM_PAINT:
        {
            HDC hdc;
            PAINTSTRUCT ps;

            RECT rc;
            GetClientRect(hwnd, &rc);           

            hdc = BeginPaint(hwnd, &ps);
            // drawing here
            EndPaint(hwnd, &ps);
            break;
        }
        default: 
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return 0;
}

方法2:

LRESULT CALLBACK wpMainWindow(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    RECT rc;

    switch (msg)
    {
        case WM_PAINT:
            GetClientRect(hwnd, &rc);

            hdc = BeginPaint(hwnd, &ps);
            // drawing here
            EndPaint(hwnd, &ps);
            break;

        default: 
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return 0;
}

在方法1中,如果在调用wpMainWindow函数时msg = WM_PAINT,它是否在开始时为堆栈上的所有变量分配内存?或者只有当它进入WM_PAINT范围时?

当消息是WM_PAINT时,方法1是否只使用内存,而无论msg等于什么,方法2都会使用内存?

9 个答案:

答案 0 :(得分:57)

变量应尽可能在本地声明。

在函数顶部声明变量总是一种灾难性的不良做法。即使在C89 / 90语言中,变量只能在块的开头声明,最好将它们声明为尽可能局部,即在覆盖变量所需寿命的最小局部块的开头。有时甚至可能引入一个“冗余”的本地块,其唯一目的是“本地化”变量声明。

在C ++和C99中,可以在代码中的任何地方声明变量,答案非常简单:再次,将每个变量声明为尽可能本地,并尽可能接近您使用它的点。第一次。这个的主要原理是,在大多数情况下,这将允许您在声明点为变量提供有意义的初始化器(而不是在没有初始化器或使用虚拟初始化器的情况下声明它)。

对于内存使用情况,通常一个典型的实现将立即(当您输入函数时)分配同时存在的所有变量所需的最大空间。但是,您的声明习惯可能会影响该空间的确切大小。例如,在此代码中

void foo() {
  int a, b, c;

  if (...) {
  }

  if (...) {
  }
}

所有三个变量同时存在,并且通常必须分配所有三个变量的空间。但是在这段代码中

void foo() {
  int a;

  if (...) {
    int b;
  }

  if (...) {
    int c;
  }
}

在任何给定时刻只存在两个变量,这意味着只有两个变量的空间将由典型实现分配(bc将共享相同的空间)。这是将变量声明为尽可能本地的另一个原因。

答案 1 :(得分:12)

在案例1中是否在堆栈上分配的内容是实现定义的。实现甚至不需要 来堆叠。

通常没有较慢这样做,因为操作往往是整个局部变量区域的堆栈指针中一个值的简单减法(对于向下增长的堆栈)。

这里重要的是范围应该尽可能地本地化。换句话说,尽可能晚地声明您的变量,并且只要需要它们就可以保留它们。

请注意,此处的声明处于不同的抽象级别,以便为它们分配空间。实际空间可以在函数的开头(实现级别)分配,但是只有在它们的作用域(C级)时才能使用这些变量。

信息的位置很重要,就像它的堂兄,封装一样。

答案 2 :(得分:7)

我喜欢方法3:

LRESULT wpMainWindowPaint(HWND hwnd)
{
    HDC hdc;
    PAINTSTRUCT ps;

    RECT rc;
    GetClientRect(hwnd, &rc);           

    hdc = BeginPaint(hwnd, &ps);
    // drawing here
    EndPaint(hwnd, &ps);
    return 0;
}

LRESULT CALLBACK wpMainWindow(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
        case WM_PAINT:      return wpMainWindowPaint(hwnd);
        default:            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
}

如果它应该有自己的组织范围,它应该有自己的功能。如果您担心函数调用开销,请将其设置为内联。

答案 3 :(得分:5)

由于编译器的工作是优化我的代码,一小时的编译时间比一小时的时间便宜,如果我需要向上和向下滚动代码以查看变量的位置,我的时间会浪费宣布,我认为我的公司希望我尽可能保持所有地方。

我甚至不是在谈论'最小块',而是'靠近使用它的地方'!

LRESULT CALLBACK wpMainWindow(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) 
{ 
    switch (msg) 
    { 
        case WM_PAINT: 
        { 
            RECT rc; 
            GetClientRect(hwnd, &rc);            

            { // sometimes I even create an arbitrary block 
              // to show correlated statements.
              // as a side-effect, the compiler may not need to allocate space for 
              // variables declared here...
              PAINTSTRUCT ps; 
              HDC hdc = BeginPaint(hwnd, &ps); 
              // drawing here 
              EndPaint(hwnd, &ps); 
            }
            break; 
        } 
        default:  
            return DefWindowProc(hwnd, msg, wparam, lparam); 
    } 
    return 0; 
} 

答案 4 :(得分:3)

在最相关的最窄范围内定义变量。在我看来,没有理由使用上面的方法2。

只有当变量在范围内时才可能使用堆栈空间。正如@paxdiablo指出的那样,如果编译器可以为它们找到空间,那么你的本地人可能会在寄存器而不是堆栈中结束。

答案 5 :(得分:1)

标准中没有为此详细信息指定内存分配,因此要获得真正的答案,您必须指定编译器和平台。这对性能无关紧要。

你想要的是可读性,一般来说,这是通过在最小的可用范围内声明变量来完成的,最好是你可以用合理的值立即初始化它们。变量范围越小,它就越不可能以不可预测的方式与程序的其余部分进行交互。声明越接近初始化,发生任何不良事件的机会就越少。

可能更好的是像

RECT rc;
GetClientRect(hwnd, &rc);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);

这适用于C ++。对于C,规则是类似的,除了早期版本的C要求所有变量都在块的顶部声明。

答案 6 :(得分:1)

您无法知道堆栈预留在什么时候完成。

为了便于阅读,我会使用C99(或C ++)。这允许你在第一次使用变量时声明变量。

 HDC hdc = BeginPaint(hwnd, &ps);

答案 7 :(得分:0)

不需要使用可能从未使用的变量污染堆栈。在使用之前分配您的变量。忽略RECT rc以及随后对GetClientRect的调用,Ben Voight的方法是可行的。

答案 8 :(得分:0)

对于Java编程语言,通常的做法是仅在方法中需要时声明局部变量。

void foo(int i) {
  if (i == 1)
    return;
  Map map1 = new HashMap();
  if (i == 2)
    return;
  Map map2 = new HashMap();
}

对于C ++编程语言,我也建议使用相同的实践,因为使用非平凡的构造函数声明变量涉及执行成本。如果将使用其中一些变量,则将所有这些声明放在方法开头会导致不必要的成本。

void foo(int i) 
{
  if (i == 1)
    return;
  std::map<int, int> map1; // constructor is executed here
  if (i == 2)
    return;
  std::map<int, int> map2; // constructor is executed here
}

对于C来说,故事是不同的。这取决于架构和编译器。对于x86和GCC,将所有声明放在函数开头并仅在需要时声明变量具有相同的性能。原因是C变量没有构造函数。并且这两种方法对堆栈内存分配的影响是相同的。这是一个例子:

void foo(int i)
{
  int m[50];
  int n[50];
  switch (i) {
    case 0:
      break;
    case 1:
      break;
    default:
      break;
  }
}

void bar(int i) 
{
  int m[50];
  switch (i) {
    case 0:
      break;
    case 1:
      break;
    default:
      break;
  }
  int n[50];
}

对于这两个函数,堆栈操作的汇编代码是:

pushl   %ebp
movl    %esp, %ebp
subl    $400, %esp

将所有声明放在函数开头是Linux内核代码中常见的。