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

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

Хороший пример. Боюсь, что если бы мне пришлось где-то использовать sizeof(*this), я бы сделал так же.
Передавайте в sizeof() имя типа, а не объекта — и подобных глюков не будет.
Забавно, что в Google C++ Style Guide написана обратная рекомендация:
Use sizeof(varname) instead of sizeof(type) whenever possible. Use sizeof(varname) because it will update appropriately if the type of the variable changes. sizeof(type) may make sense in some cases, but should generally be avoided because it can fall out of sync if the variable's type changes.

google-styleguide.googlecode.com/svn/trunk/cppguide.xml?showone=sizeof#sizeof
И это правильно — пример:
ABC * x, * y;
...
memcpy(x, y, sizeof(ABC));

А завтра другой разраб расширил это, и поправил только там где вылетала ошибка компилятора — теперь компилится же…
ABCExtended * x, * y;
...
memcpy(x, y, sizeof(ABC));
Передавать имя типа несколько опасно. Если поменяется тип у this или просто переменной 'x', например. То легко можно забыть поменять в этом memset/memcpy на новый тип.
Я в коде указываю
sizeof(TprintPrefs)
всегда, название класса. Но чаще есть
typedef ClassName type;
и далее используется
sizeof(type)
Не ошибка компилятора, но все-же пакость:
MVS 2010:
__is_pod(int) = false
__has_trivial_copy(int) = false

GCC в обоих случаях дает true, в чем абсолютно прав.

Я бы не стал так категорично:
POD (for Plain Old Data) — that is a struct or class without constructors, destructors and virtual members functions.

Думаю по этому поведение __is_pod на фундаменталках неопределено, хотя msdn прямо говорит нам следующее:
__is_pod returns false on fundamental types.
Ну вот в с++11 ослабили и усовершенствовали понятие POD, до разумного, в котором int таки POD. А вот что делать же с 98…
Мне кажется, что код, где нужен __is_pod(int), это код с запашком. Понимаю, что бывает нужно. Но всё равно есть такое ощущение.
Нужно для memcpy rellocation, чтобы определить можно ли запускать memcpy для переноса множества объектов в другой кусок памяти, внутри контейнеров. Для POD можно, для всех других — нельзя, за исключением спец. указаний посредством type traits.
Шаблоны же. Когда вам передают в шаблон T, то вы вряд ли захотите делать особые случаи для всех builtin типов.
В конечных приложения как правило is_pod не нужен. А вот в библиотеках он очень полезен. Хотя бы для реализации того же std::copy. Хотя там применяется менее строгий type trait.
Библиотеки контейнеров часто (в gamedev) самописные.
Ну и какого, MVC думает что у int не тривиальное копирование? POD бы я еще хоть как-то понял…
__has_trivial_copy не говорит про копирование — __has_trivial_copy говорит есть ли у класса «compiler-generated copy constructor».
Поэтому, по той же причине, что и __is_pod на фундаменталках неопределено… без конструктора же.
Ну так вот, gcc говорит что true для POD-ов и еще там всякого немного. Основном прикол в том, что эти функции не из стандарта, что позволяет компиляторам делать все, что они пожелают нужным. GCC сделал все правильно, а в MVC все так, что я даже не знаю как и зачем его можно использовать((.
Просто надо использовать не внутренние type trait'ы, а те, что добавили в C++11.
Чтобы поправить проект с 98 на 11 нужно много времени. Оставлять двух-стандартным — плохое решение.
Оно то и так запустится, но нужно же дух с++11 внедрить, типизированные структуры, хорошие POD, лямбды… А для этого нужно по всему коду…
*типизированные enum-ы
Дух C++11 внедрить действительно трудозатратно. Но лучше местами использовать новые фичи, не поменяв дух, чем местами использовать недокументированные фичи, оять таки не поменяв дух.
Кроме того, даже просто собрав с поддержкой нового стандарта можно получить прирост производительности, за счет использования move семантики классами из STL.
Уточню у lead-а, есть смысл в ваших словах.
Прошу прощение, везде попутал MVS = MVC = Microsoft Visual Studio
Ответ от lead-а:

(14:54:39) JohnBrown: С++11 игнорирую, потому что поддержка компиляторами имеет тенденцию быть паршивой, особенно не на PC
(14:55:00) JohnBrown: предпочитая «проверенные временем» варианты
(14:55:17) JohnBrown: как будет ясно, что все интересные платформы умеют С++11, можно будет и переходить
(14:55:26) JohnBrown: а пока и PC компиляторы целиком-то не умеют
(14:55:28) JohnBrown: даже GCC
(14:55:34) JohnBrown: а уж MSVC и тем более
Поддержка компиляторами действительно еще не устоялась и не совсем полна. Но базовые вещи уже есть в большинстве PC компиляторов. У нас нет необходимости поддерживать много разных, в том числе устаревших, платформ, поэтому мы можем в своих приложениях использовать те фишки, которые есть в gcc.
Кроме того, некоторые новые возможности давно доступны в виде расширений или в boost. Поэтому разработчикам компиляторов добавить их было легко и быстро, причем они уже обкатаны и оттестированы. Зачастую это еще и наиболее употребимые фичи.
Целевая аудитория — gcc, clang, MSVC. boost не используется. Собственно, мы выкрутились с той ситуации с подами, и пока все хорошо. Особого смысла перехода пока что нет.
Интересные заявления. Мсье Джон про clang слышал? Это я про «особенно не на PC». И тот факт, что MSVC поддерживает все новые заголовки STL и, за исключением, variadic template в MSVC присутствуют все мажорные фичи C++11, тоже не в пользу Джона говорит.
Тут в общем каждый сам решает, но лучше писать на немного обрезанном С++11, чем на С++03.
Мсье Джон слышал про clang, и проект отлично под ним компилится. Особенно не на PC — имелось ввиду на всяких консольках, в которых GCC весомо устарел.

Заходим на wiki.apache.org/stdcxx/C++0xCompilerSupport и видим печаль в колонке MSVC, о чем и пытался сказать Джон.

Да нет там никакой печали. Давайте прямо, чего из мажорных фич, помимо вариадиков, нет в MSVC? Без которых использование C++11 затруднено.
Ну даже если бы там было ничего, можно было бы юзать, не особо утруждаясь. Ну вот типизированный enum, который мы хотели будет только в MSVC 2012. А про то, как там все работает и что это за студия будет…

Джон все-же утверждает, что не против С++11, но нужно много чего проверять, и пока это не имеет высокого приоритета.
Если что, свое мнения по этому поводу у меня нынче практически отсутствует. Учусь у старших.
А что 2012 студии еще нет в паблике разве? Студия отличная, enum class работает. Как и многое другое.
На хабре, справа, в баннере написано, что будет через 6 дней.
Нет, например, новых enum'ов. А шаблоны с переменным числом аргументов — это не «кроме», а одна из ключевых фич. В частности, из-за отсутствия, не реализованы полностью emplace-методы в STL-контейнерах.
Есть новые енумы.
Я говорю о VS2010, а не о 2012, релиз которого еще не состоялся. Новую студию нужно будет покупать еще раз, а стоит ли оно того? Полной поддержки С++11 нет и там, тех же variadic templates нету
Есть Express версия. Так, что можно и не покупать.
Релиза может и нет, зато RC давно в паблике есть. Да и релиз скоро туда же пойдёт.
VS 2010 больше не считается, Вы дже на неё баг создать не можете, поэтому и говорить о ней нечего.
Я говорю о разработке коммерческого продукта — о каком Express может идти речь?
В чем проблема собирания коммерческого продукта на Express?
Давайте быть честными. Грабли есть.
Если используется boost, так старые версии не собираются новейшим gcc в режиме C++11. Обновить boost нельзя: с ним собрана половина пакетов LTS дистрибутива, мы не будем их пересобирать. Совместимость в boost не гарантируется даже между минорными версиями.
Часть библиотек, по старинке, использует boost, часть такие же свежие сочные фичи из std::. Когда они встречаются в одном бинарнике, их надо как-то увязывать.
std::unordered_map из gcc 4.6 бинарно несовместим с std::unordered_map из gcc 4.4 (бинарно-совместимая версия лежит в tr1/unordered_map)
gcc 4.7 бинарно-несовместим с предыдущими версиями. Для соблюдения новых требований стандарта пришлось поменять реализации некоторых контейнеров, например std::list теперь помнит свой размер.

Но в целом, новшества настолько назрели, что использовать их как правило весьма хочется.
Как то так…
template<typename T>
struct __IsFundamentalType { enum { result = false }; };
template<>
struct __IsFundamentalType<int> { enum { result = true }; };
template<>
struct __IsFundamentalType<char> { enum { result = true }; };
... повторить для всех фундаменталок ...
template<>
struct __IsFundamentalType<void> { enum { result = true }; };

template<typename T>
bool __is_basic(const T& var)
{
    return (__IsFundamentalType<T>::result);
}
#define __is_basictype(T) (__IsFundamentalType<T>::result)
угу. и unsigned long long int нельзя забыть, думал и так, но все-же это не торт. Хотя именно такой вариант вроде в boost-е?
Схожий вариант и в бусте, и в стандарте. По другому is_integral никто бы делать и не стал.
А еще много открытий чудных нам готовит алиазинг:
In the following example code, a float pointer *f is created as an alternative way to access the integer variable i. That is flawed code, of course, but even from experienced programmers I’ve heard the opinion that it would work nevertheless in two cases: Setting a variable to zero is deemed to be safe because on most architectures the binary representation of a floating point zero is identical to that of an integer zero.

#include <iostream>

int main() {
  int i = 3;
  float *f = (float*) &i;

  std::cout << i << " -> ";

  *f = 0.;
  std::cout << i << std::endl;
}

The naively expected output of that program would be 3 -> 0, and that’s exactly what is displayed when running an executable compiled with -O0 or -O1. However compiling with -O2 or higher, the assignment to *f is dropped because there is no subsequent read access to that variable, resulting in the output of 3 -> 3.
Искать ошибки в программе хорошо, но ровно до тех пор, пока не найдешь ошибку компилятора.
Вопрос: чему равно z (код на С++)?
int i = 1;
int k = 32;
int z = i >> k;

Тот, кто скажет, что это 0, в принципе, будет прав. Но не все компиляторы с ним согласятся (если не ошибаюсь, Borland C 3.1 имеет другое мнение, хотя в watch и правильно вычислит).

Всплыло, когда в рамках лабораторной делал собственную реализацию математических операций над типом float.
Это undefined behavior в чистом виде.
Результаты не определены, если сдвиг происходит на величину большую или равную числу двоичных разрядов.
Согласен, под «undefined behavior» это попадает более чем.

Но 2 >> 32 будет 2, а 2 >> 33 будет 1.
Такое ощущение, что для сдвига брались первые 5 бит 2го операнда (возможно, оптимизация была?).
А watch показывал 0 всегда, какую бы величину я не задал.
Я правильно понял, что вы согласны что это UB, но по-прежнему удивляетесь, что код ведет себя странно? oO
Да, я согласен, что это UB.
Но тем не менее мне интересно, почему он ведет себя именно так ).
Потому что так работают инструкции shr и shl процессора. Они используют только последние 5 бит от сдвига.
Ну что же.
Осталось найти разработчика Borland C 3.1 и спросить, почему же watch показывал правильный ожидаемый результат (это была основная причина, почем я именно компилятор подозревал).

Помнится тогда хотел проверить, не является ли это особенностью команд shr и shl, похоже, так и не проверил. Зато сейчас все окончательно ясно.
наличие UB эквивалентно «может произойти всё что угодно из каких угодно соображение и фаз луны и компилятор всегда будет прав».
Вот о том и статья! :)

В данном случае, из списка надо выбрать:
  • выход за границы массива;
  • неинициализированная переменная;
  • опечатка;
  • ошибка синхронизации в параллельной программе;
  • использование не volatile переменной, там, где надо;
  • написан код, приводящий к неопределённому поведению;
  • и так далее.


Более подробно про сдвиги я писал здесь: "Не зная брода, не лезь в воду. Часть третья".
Ну вот…
Спустя столько лет узнал, что «сам дурак», а не компилятор.
Хотя бы ради этого стоило написать :)

P.S. спасибо, прочту обязательно.
Ни разу не видел — все мне известные компиляторы (в том числе и старик Borland) сделают из этого 0.
Все намного хуже при отрицательных значениях:
int z = -255 >> 32;

Некоторые компиляторы сделают из этого -1, а некоторые 0. Хуже того — это еще иногда и платформозависимо. Сделано кстати в целях сохранить знак…
С тех пор как узнал пишу всегда как-то так:
int z = (unsigned)i >> k;

Особый шик — сдвигать на отрицательно число разрядов.
Сразу понятно, что х**ня будет. Нельзя так делать.
А я в своё время наткнулся на код, который убивал компилятор (тогда это был VS8):
struct SomeVector2
{
  float x, y;
  inline SomeVector2():x(0.0f), y(0.0f){}
};

struct SomeRect
{
    union{struct{float x, y;}; struct{SomeVector2  pos;};};
    union{struct{float r, b;}; struct{SomeVector2 epos;};};
};

const SomeRect FullQuad = {0.0f, 0.0f, 1.0f, 1.0f};

int main(int,char*[])
{
  return 0;
}


При попытке скомпилировать это Visual Studio 8 компилятор падал. VS10 и VS12 не знаю.
Плохой негодный код — класс с конструктором внутри union.
Знаю. Писал давно — когда был маленький и глупый :-)
Это не оправдывает падение компилятора. У меня к стати, был похожий случай с gcc.
> Когда программист говорит, что причиной ошибки является компилятор, в 99% случаев, он врёт.

скорее уж в 99.9%
Жесть — надеюсь у умельца нет виртуальных методов — а то вдруг memset он поправит )))
А даже если не поправит — sizeof(this) хватит, чтобы затереть ссылку на табличку (в случае некоторых компиляторов :) )
А самое главное, что причина этих ошибок — преждевременная оптимизация. Компилятор сам воспользуется быстрыми функциями копирования, если тип тривиально копируем. А код при этом становится намного понятнее.
clang на этот код выдаёт варнинг по дефолту:
/tmp/zzz.cc:6:28: warning: 'memset' call operates on objects of type 'S' while the size is based on a different type 'S *'
      [-Wsizeof-pointer-memaccess]
    memset(this, 0, sizeof(this));
           ~~~~            ^~~~
/tmp/zzz.cc:6:28: note: did you mean to dereference the argument to 'sizeof' (and multiply it by the number of elements)?
    memset(this, 0, sizeof(this));
                           ^~~~
Мне кажется, что компилятор обвиняют в последнюю очередь, когда своих идей уже нет.
Я сейчас обитаю в мире .Net и переодически сталкиваюсь с BadImageFormatException после сборки программы.
99% — это всё-таки завышенная цифра.

Да и вообще, надеяться на компилятор слишком оптимистично:
blog.regehr.org/archives/696
Подтверждаю статистику автора статьи — за 10+ лет программирования на С++ нашел баг в компиляторе только однажды. Всё остальное — баги в коде, незнание стандартов, не реагирование на ворнинги и т.д.
Ну значит плохо искали, я за вдвое меньший срок отправил ~5 багрепортов в MS Connect. И это только то, что лично я нашел.
В компиляторе С++? Какой версии? В чём состоят баги? И что о них сказали на той стороне?
В реализации C++, не соответствии имплементации стандарту. Большая часть относилась к STL. Я сейчас не могу описать все, т.к. удалил с dashboard, но вот один именно компиляторный баг.
Естественно я говорю о том, что MS приняла как баги, а не просто те, что я отослал им.
Это ошибка компиляции кода, а не неверный сгенерированный код.
не спорю. Но баг то компиляторный. Я прекрасно понимаю, о чем Ваша статья. Но комментарии о «непогрешимости» компиляторов, я полагаю, всё же не об этом. Из неверного кода у меня 2 примера:
раз
два

Оба бага из STL
std::string str("20");
std::cout << str + 12;


Жесть какая. Мы что, в Матрице PHP / JS?
Вы о чем?
А баги ваши, во-первых, не касаются неверной генерации кода, а во-вторых, были найдены, я там понимаю, еще в бета-версиях компиляторов? Тогда не удивительно, конечно.
Ну во-первых в моём комментарии русским по белому написано: баги в STL. Во-вторых, один баг из беты, другой из релиза.
Хочу заметить, что багов в Visual Studio, MSDN, примерах кода, библиотеках и т.д. я находил тоже много, но вот чтобы именно в компиляторе — только однажды.
А в C++ разве можно чистить память по указателю this? Что стандарт на этот счет говорит?

this разве всегда-всегда указывает на начало области данных в классе?
Да, в POD типах
ОК, вспомнил, да.

Но TprintPrefs может и не быть таким, верно?.. Из показанного кода неясно — POD или не-POD, так?

Я к тому, что, может быть, приведенный код вообще неверен, если TprintPrefs не является POD.
и в конструкторах, переназначениях new и private инициализаторах.
поправка: конечно если базового класса нет или POD
Я думаю о таких ошибках в первую очередь нужно сообщить разработчику программы, а не на хабр.
А я реально напарывался на баг у линкера и компилятора Borland C++… Б-же, как давно это было ^_^
Вы просто пишете на хороших компиляторах. Стоит отойти от настольных компьютеров и ситуация становится веселее.
Знакомо… один раз до умопомрачения мучился с AVR-GCC, падонок неправильно компилил небольшой кусок кода — в SREGе не востанавливал флаг I (запрета прерываний) и все тут… отправил баг репорт и переписал на ASMе кусок.
Вообще тендирую последнее время embeded писать на ASMе — хотя знаю что не комильфо и буду переписывать потом под другой чип снова, но так задрали эти компиляторы — в ASMе я по крайней мере знаю, что оно и как оно будет делать.
Не могли бы про SREG поподробнее? Чем багрепорт завершился? С ATOMIC_BLOCK(ATOMIC_RESTORESTATE) проблем не замечал.
Было это в gcc4.1, код выглядел как-то так:
#include <interrupt.h>
unsigned int ivar;
void bug( unsigned int val )
{
  ...
  val = 65535U / val;
  cli();
  ivar = val;
  sei();
  ...
}

Цель была как можно быстрее разрешить прерывания. Компилятор же утаскивал sei х** знать куда, и несколько десятков тактов прерывания терялись.
Причем ivar не важен как volatile, да и не помогало, даже переписал как atomic кое-что — безрезультатно. Замена на asm volatile("cli")" и asm volatile("sei") тоже не давала результатов.
Веселье началось по переезду на gcc4.1 с gcc3.4.6, где оно прекрасно работало.
Кто силен в немецком — здесь дискусия одного знакомого, праведно возмущенного сим фактом.
Про Bugreport забыл за давностью.
Интересно, таблица виртуальных методов потрется?
Там нет. Но вообще, так тоже делают. :) Чего я только не видел.
Разумеется! Между прочим, она-то по нулевому смещению и лежит… Так что memset(this, 0, sizeof(this)) переводит объект в состояние «был вызван деструктор» (если у объекта есть виртуальные методы).
[откровение] Это где, это в сях?! [/откровение]
Во первых там лежит (если вообще лежит) толко указатель на нее, как-то так:
void* vptr = *(unsigned long**)this;
Есть компиляторы, которые ложат его в this -4(-8) байт, т.е. new «инкрементирует» this, а delete «декрементирует» this, перед тем как удалить.
А есть компиляторы, которые виртуалку не держат в объекте вообще — только в его классе.
А ваше это — «был вызван деструктор» вообще надолго ввело в ступор)
Разумеется, там лежит исключительно указатель на нее, который и будет обнулен.
Этим самым обнулением, как правило, занимается и последний из деструкторов, потому последующие ошибки в случае memset и в случае раннего разрушения объекта будут одинаковые.

PS если указатель на таблицу виртуальных функций лежит не в объекте, а в его классе, то в самом объекте должен лежать указатель на класс с абсолютно аналогичными последствиями.
Деструктор не занимается обнлением ни указателя vtab, ни чего другого — только если вы сами это не написали…
Некоторые системы в стандартом операторе «delete» в режиме DEBUG затирают память (но не нулем, а как правило 0xCC или т.п.), что бы быстрее поймать баг при доступе к удаленному объекту.
Есть компиляторы, которые ложат его в this -4(-8) байт, т.е. new «инкрементирует» this, а delete «декрементирует» this, перед тем как удалить.

И чему для этого класса равен sizeof? Включает ли он ссылку на таблицу виртуальных функций? Если нет, то как он вообще определяется?

А есть компиляторы, которые виртуалку не держат в объекте вообще — только в его классе


И как же они по указателю на объект узнают его класс, чтобы при вызове добраться до таблицы? Опять же по ссылке? Которая тоже вполне может потереться по memset прямо в конструкторе?
sizeof равен тому что в классе определено, как если бы это было «struct», без учета vtab.
Вы можете копировать такой объект, с условием что цель была создана оператором «new».
Как раз узнал об этом «замаллочив» такой объект, после первого вызова virtual функции. Интереснее вопрос, как оно лежит в массиве — например (Object *)1 - (Object *)0 равно sizeof+4(+8).
я буду всегда проверять перед отправлением — имелось ввиду ((Object *)0)+1
Таблица сама не потрется, она лежит в другом месте, а указатель, ну куда ж он денется, если он есть.
По поводу memset: когда-то меня очень подвел следующий кусок кода:

int v;
memset(v, 0, sizeof(v));


Сначала v был массивом, и все работало. Но потом выяснилось, что достаточно всего одного элемента…
Оно бы и с массивом не работало…
Было бы правильно:
AnyType v;
memset( v, 0, sizeof (&v) );
блин, засунул & не туда:
AnyType v;
memset( &v, 0, sizeof (v) );
или для указателя:
AnyType * v;
memset( v, 0, sizeof (&v) );
Нет, с массивом оно все-таки работает!

int v[10];
memset(v, 0, sizeof(v));


Здесь первый аргумент неявно преобразуется в &v[0], а sizeof(v) == 40 в силу того, что v — все-таки не указатель, а массив.
точно — ступил после int…
Как то портировал один проект с вижуалки под GCC. Столкнулся с моментом, когда вижуалка спокойно копировала объект класса с приватным конструктором копирования.
Сделайте вывод универсальным:

Если что-то работает не так — ищите!
Давно уже взял за правило не сваливать ошибки на кого — то или что — то. Всегда найдутся корни проблемы и соответствующее решение. Но когда мало времени или лень — то по-прежнему пишется костыль с комментарием //! I @todo убрать костыль… А что поделать…
> использование не volatile переменной, там, где надо;

Использование volatile может легко попасть в те самые 99%. Почти всегда, когда люди используют volatile, они должны использовать atomic вместо этого.
Это ошибка компиляции кода, а не неверный сгенерированный код.
Хочу уточнить. Статья о том, что если что-то работает не так, то скорее всего ошибка в коде, а не в компиляторе. Тут многие пытаются доказывать, что ошибки в компиляторе тоже встречаются. Так ведь никто и не спорит! Мы в Clang, например, отписали не мало ошибок, связанных с препроцессором. Однако, всё это приводит к невозможности компиляции. А вовсе не к тому, что сгенерируется неверный код.
Я бы даже сказал так:
Когда программист говорит, что причиной ошибки является компилятор, в 99% случаев он
— начинающий программист.

Неоднократно наблюдал такую эволюцию программистов:
  1. Совсем новичок: «Не работает. Может я где-то напортачил?»
  2. Набрался чуть-чуть опыта: «Не работает. Да что ж оно все глючит-то?!» (стадия, описанная в статье)
  3. С опытом пришло некое понимание. «Не работает. Наверное компилятор глючит. Но пока громко об этом кричать не буду, сначала проверю свой код.»
  4. Достаточно опытный программист: «Не работает. Может я где-то напортачил?»
Не далее как вчера обнаружил ошибку в JIT-компиляторе Oracle JVM, где как раз генерируется неправильный код, приводящий к Segmentation Fault. Перед этим два дня тщетно искал ошибку в своей программе. Зарепортил баг.
У вас там core не может писать dump, а было бы не безинтересно взглянуть… добавте «ulimit -c» перед запуском jvm…
В дампе ничего интересного нет. Проблему можно воспроизвести из исходника, а неправильно сгенерированный ассемблерный код увидеть тут.
Статья видимо про начинающих программистов, которые готовы кого угодно винить за свои ошибки. Мне вот в голову даже не придет. В крайнем случае я буду ковырять ассемблерный код, что бы понять причину.
Говорите компилятор свят и непогрешим? А вот хрен!
Пример номер раз:
const char* foo = []() -> const char*
{
static const char test[] = { 'a', 'b', 'c' };
return test;
}();
Компилируем в VS10, получаем падение компилятора.
Еще пример креша здесь.
Каждый кто пробовал заниматься нетривиальным метапрограммированием на шаблонах знает насколько глючным может быть компилятор, какую лажу он иногда выдает и как часто падает от двух безобидных строчек кода.
h[ttp]://stackoverflow.com/questions/4290351/why-does-vc-2010-compiler-crash-when-compiling-simple-code
Допост. Парсер считает что у меня мало кармы чтобы оставлять ссылки.
Только что нашел в старых svn ревизиях — такая интересная «ошибка компилятора», помню ржали всем отделом…
#define CalcFunc(k,n) ((k*n) / (k*2 + ++n))
...
// workaround - cause of compiler bug, we save value of "x" :
long j = x;
z = CalcFunc(y, x);
x = j;
...


А функция кстати симпатичная.
Кстати, само вычисление функции содержит UB. (k*n) может быть вичислено как до ++n, так и после.
Поясните не программисту в чем прикол.
Если грубо — макро-функция это не функция — препроцессор заменит «вызов» на соответствующий ей кусок, еще до компиляции. Т.е. инкремент «n»(«x») происходит в теле программы а не в теле «функции»…

«Непрограммист» интересуется ошибками компиляторов?
По профессии нет, но на досуге балуюсь for fun. 8)

Как это обработает препроцессор и так понятно. Я не сразу просек, что им это инкрементирование на самом деле не нужно, и поэтому вставлена эта фигня j = x; x = j;
Самому в голову бы вообще не пришло использовать инкременты в макросах. Правильно говорят, что нет ничего хуже чем разбирать чужой код.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации