Как стать автором
Обновить

Комментарии 77

НЛО прилетело и опубликовало эту надпись здесь
const это просто сообщение другим программистам (и самому себе через N времени), что «я не собираюсь это менять в этой части программы». Оно совершенно не значит, что объект не будет изменён где-то или когда-то ещё.
Для второго случая (и в том числе для оптимизаций) в более других языках придумали final. Либо завезли const сразу со смыслом final.

В тех контекстах, в которых final применимо, const тоже справляется.


Если искать что-то более мощное — надо смотреть в сторону контроля времени жизни и правил заимствования.

В тех контекстах, в которых final применимо, const тоже справляется.

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

А самый важный фактор, имхо, зачем использовать конст в некоторых местах — это то, что константные объекты доступны только на чтение и соответственно должны быть потокобезопасными.
константные объекты доступны только на чтение и соответственно должны быть потокобезопасными

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

Это понятно. Поэтому и добавил про «некоторые места».
Ниже комментарий, в котором более лаконично описано то, что я хотел сказать: «const — это про семантику, а не оптимизацию»

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

const correctness. Я тоже ни разу не использовал const в надежде на ускорение кода. Да и вообще, иммутабельность — это очень хорошо в контексте многопоточного программирования.

Я не уверен, но на простых примерах компилятор может вообще использовать результат функции.
#include <stdio.h>
int func(const int num){
    int result = 0;
    for (int i=0; i<num; ++i){
        result += i;
    }
    return result;
}
int func2(int num){
    int result = 0;
    for (int i=0; i<num; ++i){
        result += i;
    }
    return result;
}
int main()
{
    printf("%d, %d\n", func(10), func2(10));
    return 0;
}

выхлоп llvm
define i32 @main() local_unnamed_addr #1 {
%1 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str, i64 0, i64 0), i32 45, i32 45)
ret i32 0
}

Хотя сами функции одинаковы. Возможно в более сложных примерах const позволяет чуть лучше оптимизировать.

Разве const не говорит компилятору помещать такие данные(инициализированные строки/массивы), объявленные как статические или глобальные, в Read-Only разделы памяти? На процессорах с Гарвардской архитектурой это очень важно.

Структуры с нетривиальным конструктором туда так просто не поместить.


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

И нет, const не может говорить компилятору ничего подобного

Ну почему? На конкретной платформе конкретная реализация компилятора может использовать подобное знание. Т.е. может — да, обязан — нет.

Насколько я помню, в avr-gcc (типичный пример компилятора для гарвардской архитектуры) это не так, нужно явно указывать атрибут PROGMEM.

Из драфта C11 (документ n1570):
132) The implementation may place a const object that is not volatile in a read-only region of storage. Moreover, the implementation need not allocate storage for such an object if its address is never used.

Может, но, видимо, не обязательно.
habr.com/ru/company/infopulse/blog/322320
"… закончил серию изменений в коде браузера Chrome, которая уменьшила размер его бинарника под Windows примерно на 1 мегабайт, перенесла около 500 КB из read/write сегмента в read-only, а также уменьшила потребление оперативной памяти в общем примерно на 200 KB на каждый процесс Chrome. Удивительное заключается в том, что конкретно данная серия изменений состояла исключительно из удаления и добавления ключевого слова const в некоторых местах кода. Да, компиляторы — странные."

Т.е. зато есть оптимизация по размеру?
В C объявление
void constFunc(const int *x);
бессмысленно, как и константность возвращаемого значения, константность аргумента может иметь значение только в теле определения функции.
C++, кстати, хоть и строже, но тоже может игнорировать константность возвращаемого значения и аргумента, но лень искать, где про это говориться явно.
Так что боюсь, все Ваши результаты про неизменность вызовов самоочевидны и без изучения ассемблера.
Сорри, поспешил. Указатель на константу — это, конечно работает. В C нет смысла в константах-возвращаемых значениях в объявлениях и аргументах.
В помощь минусующим цитаты из руководств компиляторов.
Это из Интел по-поводу возвращаемых значений:
Compiler generates this warning when it finds a type qualifier applied to the return type of a function. C++ allows the type-qualifier to be const or volatile, but either qualifier applied to a function return type is meaningless, because functions can only return rvalues and the type qualifiers apply only to lvalues.

Аналогичные предупреждения делают и многие другие компиляторы, хотя и не все.
Ну и выдержка из стандарта по-поводу аргументов:
Parameter declarations that differ only in the presence or absence of const and/or volatile are equivalent. That is, the const and volatile type-specifiers for each parameter type are ignored [...]

Only the const and volatile type-specifiers at the outermost level of the parameter type specification are ignored in this fashion; const and volatile type-specifiers buried within a parameter type specification are significant and can be used to distinguish overloaded function declarations. [...]

Для ссылок и указателей на константы это, естественно значимо. Тут я дал маху и поторопился.

Цитата из Интел тут ни к чему — Интел и их документация не славятся (мягко говоря) хорошим знанием языка С++. Вот и та цитата, которую вы здесь привели — безграмотная чушь. Эту чушь еще можно было худо-бедно подогнать под стандарт в рамках старинного С++98, но сегодня эта безграмотная чушь — безнадежна.


Цитата из стандарта языка, которую вы привели, описывает процесс формирования типа функции для целей overloading. Ни больше не меньше. Это совершено узкая, изолированная и посторонняя тема, не имеющая никакого отношения к рассматриваемому вопросу. К чему вы здесь ее привели?


В общем же ни о каком "ignored" для квалификаторов параметров и возвращаемого значения в С++ речи быть не может — они ни в коем случае не ignored (!).

Оба утверждения — не верны и являются популярной "пионэрской твердилкой".


Константность параметров, как вы сами сказали, влияет на семантику параметра в теле функции. В частности, такая константность активно и широко используется в coding standards, которые запрещают менять значения параметров внутри функции.


Константность возвращаемого значения действительно бессмысленна лишь для скалярных типов. Как только возвращаемое значение становится класс-типом эта константность тоже начинает влиять на семантику.

Ну понятно, что документация компилятора Intel для Вас — так, пустое словоблудие, ну что они там понимают)

Про возвращаемое значение: скалярность и нескалярность не имеет значения, важно лишь возвращаем мы значение или ссылку/указатель. Возвращяемое значение — всегда rvalue, и константность/волатильность тут применена не может. Если мы возвращаем ссылку/указатель, то квалификатор того, на что они указывают, используется, т.к. мы можем обращаться к объекту через указатель, который сам по себе является rvalue.
В прототипе функции то же самое: если там значение, то квалификатор никак не используется, прототипы U f(T i); и U f(T const i); эквивалентны. А вот в определении параметры — это объявление локальных переменных и квалификация учитывается компилятором.
Так что нет, руководство компилятора Intel право, а Вы ошибаетесь: скалярный тип или класс — неважно, роль играет только возврат/передача параметра по значению или по ссылке. Что и требовалось доказать.

Да, именно словоблудие! Причем на словоблудие это я/мы им указывали не раз и по своей инициативе, и в процессе оплаченных Интелом (!) же ревью их документации, но воз, как говорится, и ныне там. Но основная проблема, боюсь, не в интеловской документации, а лично в вашем неумении понимать прочитанное.


Про возвращаемое значение — совершенно неверно! Тот факт, что возвращаемое значение является rvalue абсолютно ничего не меняет. Зачем вы это повторяете? Еще раз: в языке С++ для rvalue класс-типов cv-квалификация совершенно полноценно применима и всегда была применима (!), как я уже ясно сказал выше. Вы же почему-то продолжаете твердить, что она якобы "тут применена не может". Что за чушь? Попробуйте, в конце концов, сами


std::string foo() { return ""; }
const std::string bar() { return ""; }

int main()
{
  foo() = "ABC";
  bar() = "ABC";
}

Замечаете разницу? То-то. А далее уже самостоятельно: вперед изучать свойства cv-квалификации в С++.


Что касается параметров, вы вдруг полезли рассказывать про эквивалентность прототипов. Это так, но к чему вдруг здесь прототипы? Прототип описывает внешнюю спецификацию функции и она действительно не меняется от cv-квалификации параметров. Но к делу это вообще никак не относится. Еще раз, как уже было ясно сказано выше: cv-квалификация параметра имеет абсолютно явный и однозначный эффект по отношению к семантике этого параметра внутри определения функции. Прототипы к этому никакого отношения не имеют. (P.S. В С++ нет "прототипов").

Насчет C++ убедили, у const std::string нет оператора присваивания с this. Хотя пример, мало сказать, искусственный, и скорее показывает «дырку» в логике языка. Я, действительно, последнее время больше имею дело с C, где такое невозможно, и там cv-квалификация возвращаемого значения не играет роли.
По поводу параметров и отсутствия прототипов функций в C++ — не убедили. Возможно, в standardize последних версий термина «прототип функции» и нет, но используется он в отношении C++ повсеместно.

const — это про семантические ограничения кода, а не про кодогенерацию. Но иногда такие ограничения могут компилятору помочь.

const в C/C++ нужен для увеличения числа случаев, когда возникает UB.

Так наоборот же! :)

Я слышал, если производительность действительно важна, то нужно было рефакторить код, чтобы добавить побольше const, даже если код становился менее читабельным

А есть пример, где const как-то необратимо снижает читабельность?
Как и уже многие написали выше, я всегда считал, что const нужен больше как раз для читабельности и задания семантики. Писать на языке без констант — боль.
Немного оффтопик, но я верно понимаю, что на КДПВ изображён С++, который хочет убить оптимизацию, но синтаксический сахар ему в этом мешает?
Молодец автор, придумал миф и опроверг его.
Вы конечно, иронизируете, но что плохого в таком поступке по сути? Даже если и придумал — то своим исследованием возможно кого-то предупредил от такого же придумывания)
Вы ж не станете отрицать, что у кого-либо вообще может в голове возникнуть вопрос «а как, собственно, const может влиять на производительность?».
void constByArg(const int *x)
{
  printf("%d\n", *x);
  constFunc(x);
  printf("%d\n", *x);
}


Это означает только, что функция constByArg не изменяет значение по указателю x. Однако никто не гарантирует, что это значение не может быть изменено вызовом функции constFunc.


int x = 42;

void constFunc(const int*)
{ 
  x = 0;
}

int main()
{
  constByArg(&x);
}

Да проще, данные не меняются, а вот сам указатель — вполне:


void constFunc(const int*& v) 
{
  v = new int(31337);
}

нужно ещё больше const что бы от этого уйти ;-)

По мне, главная польза от модификатора const — это ограничение прав. Он не дает случайно начать изменять переменные, которые не были предназначены для этого. Соответственно, главное увеличение эффективности лежит в области собственно программирования, а не исполнения.

А выигрыш в работе непосредственно на компьютере — и в самом деле, при хорошем компиляторе не должен быть сильно заметен.
Почему const не ускоряет код на С/C++?
а что, должен был ускорять?

Лично я думал, что препроцессор компилятора заменяет все упоминания константы на ее значение, то есть если
const int a = 4;

b = a * 2


то для компилятора это будет

b = 4 * 2 8

Вы путаете чем занимается препроцессор, а чем занимается компилятор. Препроцессор не разбирается с const. Он только заменит константы объявленные в #define стиле.

Вот я и говорю, я думал, что это делает препроцессор. Кстати, а что ему мешает делать такую замену? Причем я имею ввиду не аргумент, разумеется, а там где грубо говоря человек может заменить. (как в моем примере выше)

Мешает ему это делать тот простой факт, что язык препроцессора — это чуть ли не отдельный язык программирования, и const не является в нём ключевом словом.

Окей, понял. Тогда что мешает компилятору сделать то же самое? Я не сишник, помилуйте.

Это как раз одна из оптимизаций компилятора. Называется constant propagation. Для неё const не обязателен.
Ага.

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

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

Путаница вызвана в первую очередь незнанием терминологии.


В языке С есть термин "константа". Константами в С называются буквальные значения (1, 'a', 3.14) и элементы enum. Далее все покрывается термином "константное выражение". const-объекты в C "константами" не называются вообще и константных выражений не формируют.


В языке С++ термина "константа" применяется только к элементам enum. Буквальные значения называются "литералами". Все остальное выражается через термин "константное выражение". Характерно то, что в С++, в отличие от С, const-объекты формируют константные выражения.

Здесь не все так просто, в некоторых архитектурах (ARM) с константами очень плохо и поместить нетривиальную константу в регистр — это долго, так что в данном случае проще будет держать ее в близкой памяти.
const действительно позволяет C-компилятору сгенерировать более эффективный код при использовании restrict.

Какое-то очередное "открытие Америки".


Во-первых, const, разумеется, "ускоряет" код в тех ситуациях, когда он применяется к самому объекту, а не к "пути доступа" к объекту. Это прекрасно видно во всем, включая сгенерированный компилятором код. В применении же к путям доступа const действительно является лишь синтаксическим сахаром. Для оптимизации кода в таких ситуациях компилятору нужно знать полную картину aliasing, а const в этом никак не помогает. Для этого служит restrict.


Во-вторых, разглядывать для этих целей код, сгенерированный какими-то компиляторами — бессмысленно, особенно когда речь идет о GCC. GCC — отстал, крив и пионерск. Сегодня он не умеет что-то оптимизировать, но завтра — научится.

разглядывать для этих целей код, сгенерированный какими-то компиляторами

Я извиняюсь, а код сгенерированный чем вместо компилятора вы предлагаете вместо этого рассматривать?

Я предлагаю рассматривать чисто теоретические соображения о том, какие оптимизации возможны, а какие — нет в рамках стандартной семантики языков С и С++. Если оптимизация теоретически возможна в рамках данного языка, то ее практическая реализация является лишь вопросом времени. То, что какая-то оптимизация еще не реализована неким компилятором, ничего не значит.


Не забывайте, что оптимизации в современных компиляторах С/С++ реализуются не на основе некоего "суперумного самообучающегося искусственного интеллекта", а на основе банальных механизмов pattern matching. Что будет и что не будет оптимизировать компилятор зависит только от того, какой набор оптимизационных паттернов в него уже успел вбить некий условный Вася Пупкин (в промежутках между сдачами сессий), и какой набор паттернов он НЕ успел вбить (в том числе потому, что бумажка с описанием соответствующего паттерна завалилась за шкаф).


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

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

Ерундовая статья.
Сходите к Косте Осипову, если писать не о чем.

Автору должны понравиться атрибуты __attribute__ ((const)) и __attribute__ ((pure)) в терминах GCC.
Странно, впервые слышу, что const — это про оптимизацию программы. Всегда считал, что const — это про оптимизацию процесса программирования.
На мой взгляд (не претендую на абсолютное знание) эта байка про const пришла из операционок для микроконтроллеров ucLinux и иже с ним. Поскольку в микроконтроллерах размера флеша обычно хватает, а размера ОЗУ — нет, причем очень сильно не хватает, несколько килобайт — это слезы. Соответственно, переменные const кладутся во флеш, а не в ОЗУ. А чем больше остается свободного ОЗУ, тем быстрее станет доступна для выделения очередная страница памяти и тем быстрее работает система.
Причем, строго говоря, из флеша данные читаются медленнее, но ОЗУ мало, поэтому приходится жертвовать локальной оптимизацией во имя глобальной.

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

Попробуйте:
void constFunc(const int * const x);

А в чём смысл? Компилятор и так знает что переменная x не изменяется.

Ошибаетесь. Уберите второй const и попробуйте присвоить x какое- нибудь значение.
это тип данных указатель на контантное число типа int (а значит не известо, ссылается ли указаль все на тот же участок памяти, особенно в embedded такое критично):
const int * x


это указатель, который низачто не изменит адрес, на который ссылается, но вот данные могут меняться:
int * const  x


ну и как написали выше, это уже указатель, который гарантированно не изменит свой адрес и ссылается на НЕизменяемые данные:
const int * const  x
а значит не известно, ссылается ли указатель все на тот же участок памяти, особенно в embedded такое критично

Вообще-то известно: если этой переменной ничего не присваивали, а ссылка на неё никуда не утекала, то ссылается.

В том виде как используется const в этой статье — могут быть самые разнообразные результаты.
Но если привести реальный пример тотальной оптимизации, то условия будут такими:
1 Функция имеет в параметрах внешние константы — состояние которых известно компилятору. Это может быть обычные перемененные, но иницилизированные константами в пределах видимости вызова функции.
2 Функция выполняется один раз, либо функция определена как static inline. Это позволит разместить новый экземпляр функции в месте применения — сделав её одноразовой.
3 Результат вычислений должен иметь повторяемое практическое значение в общем алгоритме. В случае разного применения результата функции — оптимизация может сбойнуть.

Только в этом случае функция на константах полностью оптимизируется до одной записи результата вычислений. То-есть полностью выполняется препроцессором компилятора.

Функции на константах предназначены для замены многоэтажных зубодробительных макросов. Алгоритм в виде функции проще воспринимается и намного легче редактируется.
Функции на константах применяются в основном для смены формата имеющихся известных данных — посчитать один раз, и больше не мучится.

Автор, учите матчасть.

void foo(int *p)
{
  // Needs to do more copying of data
}

void foo(const int *p)
{
  // Doesn't need defensive copies
}

int main()
{
  const int x = 42;
  // const-ness affects which overload gets called
  foo(&x);
  return 0;
}

С одной стороны, я не думаю, что на практике это часто применяется в С++-коде.

Просто резануло глаза. Применяется очень часто.
[хреновый перевод]

> Возможно, нам нужен компилятор поумнее. Скажем, Clang.

Не равнозначен исходному тексту:

> Maybe we just need a sufficiently smart compiler. Is Clang any better?

— В оригинале автор не утверждает, что «Clang поумнее», как это сделано в переводе. В оригинале автор интересуется: «окажется ли Clang чем-то лучше?»

Почему-то не нашел в статье именно внятного ответа на вопрос "Почему" в заголовке. Давайте поиграем в телепатов и найдём правдоподобные объяснения для данного кейса с SQLite.


С языком Си понятно — const там просто обещание прогера не менять переменную после инициализацию, иначе UB будет. Что в С++:


  1. Если в каком-нибудь примере заменить const int a = 4 на int a = 4, то переменная все равно останется "effectively const", потому что ее значение в коде не меняется, и "достаточно умный компилятор (тм)" должен по идее сам отловить, что переменная и есть константа (точнее, оба варианта должны работать одинаково), неважно какой код написан. Можно считать const синтаксическим сахаром.


  2. "По идее" const/read-only данные пихаются в другой сегмент данных, например, .rdata. Но в чем смысл это делать для одной автоматической переменной? Намного быстрее оставить переменную на стеке, как если бы это был не const. И там все остальные переменные рядом, не надо лазить туда-сюда по сегментам.


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

Ускорять код const будет преимущественно тогда, когда на его месте по хорошему должен быть constexpr. Собственно зачем constexpr и был введен в C++. Ведь по сути const — это не гарантия того, что объект не может меняться. Это лишь аттрибут типа, по сути дела — часть контракта по использованию конкретно типа. Если в функцию передан указатель на const объект — функция не может его изменить.


А вот constexpr, с другой стороны, способствует массовым compile time оптимизациям, когда обращения к константам заменяются на inline значения в коде

У вас наблюдается все та же путаница между двумя принципиально разными типами константности: константностью самого объекта и константностью пути доступа к объекту.


Константность объекта — это в С и С++ всегда гарантия того, что объект не может меняться (кроме mutable членов объектов классов).

Константность — это лишь гарантия что через данный конкретный "путь", как вы выразились, нельзя обьект поменять. Пример:


  void  doSomething(const std::string& str)
  {
  }

  ...
  std::string nonConstantString { "I am not a constant string" };
  ... 

  doSomething(nonConstantString); 

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

Прекрасно, но к чему это здесь?


Еще раз: вы сделали утверждение, что "const — это не гарантия того, что объект не может меняться". Я вас поправляю: const верхнего уровня (т.е. примененный непосредственно к объекту, а не к пути доступа) является гарантией того, что объект не может меняться.


Ни больше, ни меньше.

Прекрасно, но к чему это здесь?

Точно такой же вопрос можно задать на ваш первый ответ мне.

Я вас поправляю: const верхнего уровня (т.е. примененный непосредственно к объекту, а не к пути доступа) является гарантией того, что объект не может меняться
.
К сожалению это не совсем так, есть ведь const_cast. Разработчики стандарта давно говорят что это очень плохой механизм, но убрать его нельзя потому что половину кода придеться редезайнить

Нет, это не "не так". const_cast в С++ предназначен для вполне конкретной цели — снятие константности с пути доступа к объекту, т.е. константности первого или более глубоких уровней вложенности. В С++ не существует средств снятия константности нулевого (верхнего) уровня, т.е. константности самого объекта и const_cast вам ничем здесь не поможет.


В любом случае, попытки модификации константного объекта через неконстантный путь доступа (как бы вы его ни получили) НЕ модифицируют объект, а приводят в неопределенному поведению. Так что, еще раз: в С++ const верхнего уровня является гарантией того, что объект не может меняться.

В любом случае, попытки модификации константного объекта через неконстантный путь доступа (как бы вы его ни получили) НЕ модифицируют объект, а приводят в неопределенному поведению.

Именно неопределенное поведение, т.е может как модифицировать объект, а может и access violation получить. Тут уж как повезет.

Например, такой код
struct A
{
	int a;

	A() { a = 5; }
};

const A a;

int main()
{
	const_cast<A&>(a).a = 3;
	std::cout << a.a;
	return 0;
}

компилируется (и msvc и GCC компиляторами) и печатает 3 а не 5.

А вот такой:
const int c = 5;

int main()
{
	const_cast<int&>(c) = 3;
	std::cout << c << std::endl;
	return 0;
}

просто крешится с access violation.

Это совершенно не важно, что получается в результате неопределенного поведения и как ведут себя разные компиляторы, потому что программа с неопределенным поведением не является корректной программой на языках С или С++. Анализировать поведение вашего кода и строить из него какие-то выводы — это по сути то же самое, что анализировать содержимое неинициализированной переменной. С точки зрения языка С++ в ваших экспериментах фактически нет никакой программы, а есть просто "мусор", который остался после компиляции (компилятор просто забыл или не сумел его почистить). И вы зачем-то пытаетесь не только запускать этот "мусор" (!), но еще и разглядывать результаты (!!). Зачем?


Ключевым моментом здесь является то, компиляторы С и С++ имеют право транслировать код исходя из предположения, что неопределенное поведение никогда не возникает (напр. компилятор С или С++ имеет право заменить целое d / d на константу 1 даже если есть вероятность того, что d окажется равным 0). Именно в этом заключается суть утверждения "в С++ const верхнего уровня является гарантией того, что объект не может меняться"

Хорошая статья, которая показывает, как компиляторы могут оптимизировать код?!
Зарегистрируйтесь на Хабре , чтобы оставить комментарий