2013-03-14 5 views
3

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

while (n < 10000) { 
    int x = foo(); 
    [...] 
} 

Я знаю, что стандарт абсолютно ясно, что x существует только внутри цикла, но это технически означает, что число будет выделяться и освобождаться в стеке с каждой итерацией? Я понимаю, что оптимизирующий компилятор вряд ли сделает это, но это гарантировано?

Например, он когда-либо лучше написать:

int x; 
while (n < 10000) { 
    x = foo(); 
    [...] 
} 

Я не имею в виду с этим кодом специально, но в любом виде петли, как это.

Я сделал быструю проверку с gcc 4.7.2 для простой петли, отличающейся таким образом, и была произведена одна и та же сборка, но мой вопрос в действительности состоит в том, что эти два, в соответствии со стандартом, идентичны?

+0

Как вы сами говорите о первом цикле: «Я знаю, что стандарт абсолютно ясен, что x существует только внутри цикла», и, как и во втором примере, переменная 'x' может использоваться _after_ в цикле, которые могут быть двумя невозможно быть идентичным, или стандарт был бы двусмысленным. –

+0

@JoachimPileborg: ну да, но в отношении цикла. – teppic

+0

Если вы просто рассматриваете цикл и ничего больше, то да, они идентичны. –

ответ

1

Да, переменная x внутри цикла технически определена на каждой итерации и инициализируется по вызову foo() на каждой итерации. Если foo() каждый раз дает разные ответы, это нормально; если он дает один и тот же ответ каждый раз, это возможность оптимизации - переместите инициализацию из цикла. Для простой переменной, подобной этой, компилятор обычно просто резервирует sizeof(int) байтов в стеке - если он не может сохранить x в регистре - он используется для x, когда x находится в области видимости и может повторно использовать это пространство для других переменных в другом месте ту же функцию. Если переменная была массивом переменной длины VLA, то распределение является более сложным.

Два фрагмента в изоляции являются эквивалентными, но разница составляет область x. В примере с объявлением x за пределами цикла значение сохраняется после выхода цикла. С объявлением x внутри цикла он недоступен после выхода цикла. Если вы написали:

{ 
    int x; 
    while (n < 10000) 
    { 
     x = foo(); 
     ...other stuff... 
    } 
} 

тогда два фрагмента достаточно близки друг другу. На уровне ассемблера вам будет трудно определить разницу в обоих случаях.

+0

Вы совершенно правы в отношении разницы в объеме x, конечно, из-за этого я сделал вопрос менее ясным, но ваш пример фиксирует сравнение. – teppic

6

Обратите внимание, что «выделение» таких автоматических переменных, как это, в значительной степени бесплатное; на большинстве машин это либо корректировка указателя стека с одной инструкцией, либо компилятор использует регистры, и в этом случае ничего не нужно делать.

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

Кроме того, конечно, компилятор может свободно «перемещать» выделение из петли вообще, если это похоже на то, что делает код эквивалентным вашему второму примеру с int x; до while. Важно то, что первая версия легче читать и более сильно локализовать, т. Е. Лучше для людей.

+0

Несомненно, хотя из-за того, что переменная определяется (а не просто доступна) в цикле, она должна выйти из сферы действия (по крайней мере технически)? Как иначе, следующая итерация попытается определить другой экземпляр существующей переменной? – teppic

+1

Технически, это выходит из существования. На практике это означает, что (a) вы не можете ссылаться на 'x' вне цикла, и (b) компилятор останавливается, используя пространство, которое он выделил для' x', чтобы хранить 'x' (и он может начать использовать то же пространство для хранения другой переменной в следующем коде). Вероятно, не будет никаких других изменений, кроме того, ссылается ли компилятор на место памяти или нет. –

1

Моя личная точка зрения заключается в том, что как только вы начинаете беспокоиться о таких микрооптимизациях, вы обречены на провал.Коэффициент усиления:

а) Скорее всего, будет очень мало

б) непортабельный

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

0

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

Это, как говорится, если вы не начнете делать что-то с ума Власа так:

void bar(char *, char *); 
void 
foo(int x) 
{ 
     int i; 
     for (i = 0; i < x; i++) { 
       char a[i], b[x - i]; 
       bar(a, b); 
     } 
} 

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

Смежные вопросы