哪个是优选的,方法1或方法2?
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;
}
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都会使用内存?
答案 0 :(得分:57)
变量应尽可能在本地声明。
在函数顶部声明变量总是一种灾难性的不良做法。即使在C89 / 90语言中,变量只能在块的开头声明,最好将它们声明为尽可能局部,即在覆盖变量所需寿命的最小局部块的开头。有时甚至可能引入一个“冗余”的本地块,其唯一目的是“本地化”变量声明。
在C ++和C99中,可以在代码中的任何地方声明变量,答案非常简单:再次,将每个变量声明为尽可能本地,并尽可能接近您使用它的点。第一次。这个的主要原理是,在大多数情况下,这将允许您在声明点为变量提供有意义的初始化器(而不是在没有初始化器或使用虚拟初始化器的情况下声明它)。
对于内存使用情况,通常一个典型的实现将立即(当您输入函数时)分配同时存在的所有变量所需的最大空间。但是,您的声明习惯可能会影响该空间的确切大小。例如,在此代码中
void foo() {
int a, b, c;
if (...) {
}
if (...) {
}
}
所有三个变量同时存在,并且通常必须分配所有三个变量的空间。但是在这段代码中
void foo() {
int a;
if (...) {
int b;
}
if (...) {
int c;
}
}
在任何给定时刻只存在两个变量,这意味着只有两个变量的空间将由典型实现分配(b
和c
将共享相同的空间)。这是将变量声明为尽可能本地的另一个原因。
答案 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内核代码中常见的。