Объявить переменные в верхней части функции или в отдельных областях? - PullRequest
36 голосов
/ 23 сентября 2010

Что предпочтительнее, метод 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, если msg =WM_PAINT, когда вызывается функция wpMainWindow, выделяет ли она память для всех переменных в стеке в начале?или только когда он входит в область действия WM_PAINT?

Будет ли метод 1 использовать память только тогда, когда сообщением является WM_PAINT, а метод 2 будет использовать память независимо от того, какое значение msg равно?

Ответы [ 9 ]

59 голосов
/ 23 сентября 2010

Переменные должны быть объявлены как можно локально.

Объявление переменных "наверху функции" всегда является катастрофически плохой практикой. Даже в языке C89 / 90, где переменные могут быть объявлены только в начале блока, лучше объявить их как можно локально, то есть в начале наименьшего локального блока, который охватывает желаемое время жизни переменной. Иногда может даже иметь смысл ввести «избыточный» локальный блок с единственной целью «локализации» объявления переменной.

В C ++ и C99, где можно объявлять переменные в любом месте кода, ответ довольно прост: опять же, объявляйте каждую переменную настолько локально, насколько это возможно, и как можно ближе к точке, где вы ее используете. первый раз. Основное обоснование этого заключается в том, что в большинстве случаев это позволит вам предоставить значимый инициализатор переменной в точке объявления (вместо объявления ее без инициализатора или с фиктивным инициализатором).

Что касается использования памяти, в общем случае типичная реализация немедленно (при вводе функции) выделяет максимальное пространство, необходимое для всех переменных, которые существуют одновременно. Однако ваши привычки декларации могут повлиять на точный размер этого пространства. Например, в этом коде

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

  if (...) {
  }

  if (...) {
  }
}

все три переменные существуют одновременно, и, как правило, пространство для всех трех должно быть выделено. Но в этом коде

void foo() {
  int a;

  if (...) {
    int b;
  }

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

только две переменные существуют в любой данный момент, это означает, что пространство только для двух переменных будет выделено типичной реализацией (b и c будут использовать то же пространство). Это еще одна причина для объявления переменных как можно локально.

12 голосов
/ 23 сентября 2010

Распределяется ли что-либо в стеке в случае 1, определяется реализацией.Реализации даже не требуют для того, чтобы иметь стек.

Обычно это не медленнее , так как операция имеет тенденцию быть простым вычитанием (для растущего вниз)stack) одного значения из указателя стека для всей области локальной переменной.

Здесь важно то, что область действия должна быть как можно более локальной.Другими словами, объявляйте ваши переменные как можно позже и храните их столько, сколько нужно.

Обратите внимание, что объявление здесь находится на другом уровне абстракции для выделения места для них.Фактическое пространство может быть выделено в начале функции (уровень реализации), но вы можете использовать эти переменные только в том случае, если они ограничены (уровень C.)герметизация.

7 голосов
/ 23 сентября 2010

Мне нравится метод 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);
    }
}

Если он заслуживает своей собственной области применения в организационных целях, он заслуживает своей собственной функции. Если вы беспокоитесь о накладных расходах при вызове функции, сделайте их встроенными.

5 голосов
/ 23 сентября 2010

Поскольку работа компилятора состоит в том, чтобы оптимизировать мой код, и час времени компиляции намного дешевле, чем час моего времени, и мое время теряется, если мне нужно прокрутить код вверх и вниз, чтобы увидеть, где находится переменнаябыло объявлено, я думаю, что моя компания хочет, чтобы я держал все как можно более локальным.

Даже я не говорю о «наименьшем блоке», но «как о том месте, где он используется»!

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; 
} 
3 голосов
/ 23 сентября 2010

Определите переменные в самой узкой области, где они актуальны. По моему мнению, нет причин использовать метод 2 выше.

Пространство стека может использоваться только тогда, когда переменные находятся в области видимости. Как указывает @paxdiablo, ваши локальные пользователи могут оказаться в регистрах, а не в стеке, если компилятор может найти для них место.

1 голос
/ 13 июля 2012

Для языка программирования 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.

1 голос
/ 23 сентября 2010

Вы не можете знать, в какой момент выполняется резервирование стека.

Для удобства чтения я бы выбрал C99 (или C ++). Это позволяет вам объявить переменную действительно там, где она впервые используется.

 HDC hdc = BeginPaint(hwnd, &ps);
1 голос
/ 23 сентября 2010

Распределение памяти не указано в стандарте для этой детали, поэтому для реального ответа вам нужно будет указать компилятор и платформу.Для производительности это не имеет значения.

То, что вы хотите, - это удобочитаемость, и в целом это достигается путем объявления переменных в наименьшей полезной области действия, и предпочтительно, когда вы можете немедленно инициализировать их с приемлемыми значениями.Чем меньше область действия переменной, тем меньше она потенциально может взаимодействовать с остальной частью программы непредсказуемым образом.Чем ближе объявление к инициализации, тем меньше шансов на то, что произойдет что-то плохое.

Что может быть лучше, так это что-то вроде

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

Это для C ++.Для C правило аналогично, за исключением того, что более ранние версии C требовали объявления всех переменных в верхней части блока.

0 голосов
/ 23 сентября 2010

Нет необходимости загрязнять стек переменными, которые, возможно, никогда не используются.Выделите свои переменные прямо перед тем, как их использовать.Метод Бена Войта с видом на RECT rc и последующий вызов GetClientRect - лучший способ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...