Подтвердить что ты не робот

Объявлять переменные в верхней части функции или в отдельных областях?

Что предпочтительнее, метод 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?

4b9b3361

Ответ 1

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

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

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

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

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

  if (...) {
  }

  if (...) {
  }
}

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

void foo() {
  int a;

  if (...) {
    int b;
  }

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

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

Ответ 2

Определено ли что-то выделенное в стеке в случае 1. Даже для установки не требуется наличие стека.

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

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

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

Локальность информации важна, как и ее кузен, инкапсуляция.

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

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

Что, вероятно, будет лучше, это что-то вроде

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

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

Ответ 7

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

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

 HDC hdc = BeginPaint(hwnd, &ps);

Ответ 8

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

Ответ 9

Пока вы не инициализируетесь в цикле!:)

Кроме того, метод 1. myeviltacos хорошо подводит мое мнение.

Ответ 10

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

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

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

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.