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

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

Assert((1 > 0) == 1);

Интересно, как выглядела эта строка до препроцессора
Это и есть строка до препроцессора.
Видимо проверка на соответствие компилятора стандарту. Видать нарывались…
Извините, пожалуйста, за возможно глупый вопрос.
Разве в стандартах тех лет определяется значение константы TRUE? У меня почему-то в голове отложилась концепция «0=FALSE, остальное — TRUE». Если построить транслятор, который будет выдавать результатом 1 > 0, скажем, 10 — разве это будет отклонением от стандарта?
Вот именно это они, очевидно, и проверяли — видимо, там в последующем коде где-то можено было найти что-нибудь типа такого:
if ((a>b + c>d + e>f) > 2) { ... }

Ну, типа, проверили, что по крайней мере два условия из трёх выполнены.
C89 3.3.8:
Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false. The result has type int.
Однако в первых компиляторах Си, с которыми я работал, true было равно -1. Ибо ассемблерное not 0 давало 0xFF/0xFFFF
Как вам уже ответили в соседней ветке — это лишь означает, что данный компилятор не был компилятором языка C в версии от 89 года. Word же, очевидно, полагался на этот пункт станадарта, и, для исключения сложно диагностируемых багов, проверял данное условие.
Раньше компилятор волен был использовать любые удобные ему числовые представления для логических констант. Напомню, что в древних компиляторах C типа bool не было.

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


c-faq.com/null/null2.html и вообще вся тема c-faq.com/null/ — для вас.
Я работал с компилятором Си, где NULL имел конкретный адрес, указывающий на перехватчик исключения. Так обрабатывались NPE.
Это означает лишь то, что компилятор не соответствует стандарту.
Баги зарегали?
Позняк метаться. Это уже в золотой версии.
А критикал апдейт выпустить?
это же теперь опен сорс, на гитхаб и пул реквест.
Интересно, сколько в коде есть костылей, которые нейтрализуют действие вот этих косяков? И в каком месте что поломается, если эти косяки исправить.
Возможно что и много. Где-то проскакивала статья про методологию разработки и исправления кода у этих товарищей, мол ни в коем случае не менять имеющийся код, только дописывать. Уж не помню из каких соображений это было и насколько можно было верить источнику, но… :)

P.S.: По моему, в той же статье писалось и о курьезном костыле в ядре ОС для работоспособности конкретной игры.
Насколько я помню, Спольски писал о том, что не надо переписывать с нуля.

Про баг с СимСити:

www.joelonsoftware.com/articles/APIWar.html
first heard about this from one of the developers of the hit game SimCity, who told me that there was a critical bug in his application: it used memory right after freeing it, a major no-no that happened to work OK on DOS but would not work under Windows where memory that is freed is likely to be snatched up by another running application right away. The testers on the Windows team were going through various popular applications, testing them to make sure they worked OK, but SimCity kept crashing. They reported this to the Windows developers, who disassembled SimCity, stepped through it in a debugger, found the bug, and added special code that checked if SimCity was running, and if it did, ran the memory allocator in a special mode in which you could still use memory after freeing it.
Про то, что не надо переписывать код:
www.joelonsoftware.com/articles/fog0000000069.html

It's a bit smarmy of me to criticize them for waiting so long between releases. They didn't do it on purpose, now, did they?

Well, yes. They did. They did it by making the single worst strategic mistake that any software company can make:

They decided to rewrite the code from scratch.


Заметьте, что это позиция Джоэла, а не MS
Я впервые услышал об этом от одного из разработчиков популярной игры SimCity, который поведал мне о критической ошибке в их программе: она использовала память сразу после ее освобождения. Главное табу, нарушение которого прощалось в DOS, но карается в Windows, где освобожденную память тут же стащит другое работающее приложение. Тестеры в команде разработки Windows протестировали множество популярных приложений, чтобы убедиться, что все работает без сбоев, но SymCity зависала. Они сообщили это разработчикам Windows, которые дизассемблировали SymCity, шаг за шагом в дебаггере найдя ошибку, и добавили специальный код, проверяющий наличие SymCity в памяти и запускающий распределитель памяти в специальном режиме, в котором SymCity разрешается использовать память после ее освобождения.


Отсюда.

P.S. Я всегда буду обновлять комментарии перед отправкой своего =(
Интересно было бы увидеть комментарии разработчиков этого кода к данному анализу.
«Как мы этот проект запускали… Как вспомню, так вздрогну! И как оно работает — сам не пойму! )»
БиллГейтс-ИКак-тоТакЧерезХитро...jpg
Ballmer Peak
По секрету. Уже готова статья про проверку Unreal Engine. Есть интересное. Например, код для работы с Oculus Rift видимо ещё сыроват :)
void FOculusRiftHMD::PreRenderView_RenderThread(FSceneView& View)
{
  ....
  if (View.StereoPass == eSSP_LEFT_EYE ||
      View.StereoPass == eSSP_LEFT_EYE)
  ....
}

Правда до следующего релиза PVS-Studio я не могу её опубликовать. Почему — станет ясно из статьи.

Это я для задабривания :) Ибо есть просьба к тем, кому нравятся наши заметки. Прошу давать в twitter ссылку на английский вариант статьи. И тут ещё можно подтолкнуть. Если тема пойдёт, глядишь, кто-то и из Microsoft комментарий где-то оставит.
«Кто-то из MS» комментарий может оставить и здесь, нам не жалко :)
Надо сделать спамбота, который будет проверять исходники в публичных репозиториях и автоматически писать в форумы возмущенные собщения о найденных ошибках.
Могучий Ctrl-C Ctrl-V. Ну да, они же сказали, мол-де за отполированным продуктом — через 6 месяцев приходите. А статью ждём!
Конечно это верх эгоизма, но не могли бы вы проверить один из популярных проектов «freeswitch»?
Я думаю разработчики были бы очень рады, да и комьюнити у них хорошее
>> Можно сказать, что это доисторические времена. По крайней мере, тогда не существовало стандарта языка Си.

В 1990-м уже был ANSI C89. Насколько ему соответствует компилятор — это другой вопрос.

Если честно, немного удивлен, что PVS не загнулся на far и near указателях, которые в то время очень часто встречались в 16-битном коде под интел.
Если честно, немного удивлен, что PVS не загнулся на far и near указателях, которые в то время очень часто встречались в 16-битном коде под интел.


Сами в шоке :-)
НЛО прилетело и опубликовало эту надпись здесь
Человек заикался, а вы попрекаете :-)
Шрифт Tms Rmn нрм
Так и префиксы их системной венгерки не лыком шиты, навроде lpcszw :)
Не планируете проверить Bitcoin/Litecoin?
Интересно было бы почитать, какие ошибки там найдутся
Есть у вас один недочет. Сейчас перечитал — заметил. В разделе «Неудачный вызов функции printf()».
Вот здесь про аргументы забыли, в результате чего будет распечатан мусор.

Вы неправы. Аргументы не забыли, а «мусор» будет вполне детерминированный.

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

Суть хака в том, что printf("%d") — вот именно так, без дополнительных параметров — выведет в stdout последнюю переменную, лежащую на стеке. Поэтому если вы, скажем, напишете:
void printD(int d) {
    printf("d = %d");
}

то получите вполне верный результат. Хотя это, разумеется, хак, но так как он основан даже не на стандарте языка, а на calling convention, то пользоваться им в некоторых случаях может быть приемлемо.

P.S. В днном примере параметр будет передан, видимо, не на стеке, а в регистрах, но сути это не меняет.
В зависимости от конфигурации (debug/release) компилятор либо сгенерирует пролог (push ebp / mov ebp, esp) и тогда на стеке будет ebp, либо не сгенерирует, и на стеке будет адрес возврата, который запушен позже переданных через стек параметров. О каких переменных речь, непонятно. В любом случае, это undefined behavior и должно быть устранено.
Это неудачный сокращенный пример.
В реальном коде Word а там переменных навалом. Но все равно последними объявлены не те, которые надо распечатать. Так что он распечатает действительно не то:

	int iBuff;
	int cSt;            /* number of strings we will read */
	int cStRead = 0;    /* number of strings read so far */
	int cbRead;         /* number of bytes read so far */
	int cch;            /* length of st */
...
...
	if (cStRead == cSt)
		printf("\nCorrect number of strings were read.\n");
	else
		printf("\n - %d strings were read, %d were expected (decimal numbers) -\n");


И вообще это не код Word а, а вспомогательного инструмента:
/**  DINI.C                                              **/
/**                                                      **/
/**  This program will decode .ini files for OPUS        **/
/**  (aka Microsoft Word for Windows) and print          **/
/**  them out in human-readable format.                  **/

Мне казалось, что переменные, переданные в функцию, идут после адреса возврата, а не до. А первые несколько int-ов вообще передаются в регистрах общего назначения. Это не так?
Нет, конечно. Точнее, это не всегда так, а уж в те годы — подавно. У вас регистров на 16 битной машине — раз два и закончились.

«Сначала» (старшие адреса) пушаться аргументы, справа налево для Си, потом адрес возврата, потом, если надо, формируется кадр, т.е. пушится bp, потом идут локальные переменные (младшие адреса).

FFFE | par1
FFFC | return address
FFFA | old bp
FFF8 | local var1

В простом примере выше фрейма с bp может и не быть, т.к. нет локальных переменных.

Параметры передаются в зависимости от calling convention, для Си по умолчанию — через стек. Есть fast call или у Bolandа свой register calling convention. Есть еще pascal/fortran calling convention ну и win stdcall, конечно. Про современные 64 битные calling convention речи нет — тут регистров намного больше.
После tcc (извините, что есть под руками) получаем такой код для
void printD(int d) {
    printf("d = %d");
}

void main (){

    printD(10);
    return 0;
};


00401000   55               PUSH EBP
00401001   89E5             MOV EBP,ESP
00401003   81EC 00000000    SUB ESP,0
00401009   90               NOP
0040100A   B8 00204000      MOV EAX,test5.00402000                   ; ASCII "d = %d"
0040100F   50               PUSH EAX
00401010   E8 A3000000      CALL <JMP.&msvcrt.printf>
00401015   83C4 04          ADD ESP,4
00401018   C9               LEAVE
00401019   C3               RETN
0040101A   55               PUSH EBP
0040101B   89E5             MOV EBP,ESP
0040101D   81EC 00000000    SUB ESP,0
00401023   90               NOP
00401024   B8 0A000000      MOV EAX,0A
00401029   50               PUSH EAX
0040102A   E8 D1FFFFFF      CALL test5.00401000
0040102F   83C4 04          ADD ESP,4
00401032   B8 00000000      MOV EAX,0
00401037   E9 00000000      JMP test5.0040103C
0040103C   C9               LEAVE
0040103D   C3               RETN


Стек растёт в обратную сторону, поэтому мне пришлось подбирать осторожную формулировку «адрес возврата запушен позже».
В абсолютных адресах аргументы идут после адреса возврата. В любом случае, printf сначала будет забирать аргументы с меньшим адресом на стеке, т.е. сначала адрес возврата, затем параметры ф-ции.

А первые несколько int-ов вообще передаются в регистрах общего назначения

Зависит от опции компилятора, выставляющую calling convention, для __cdecl и __stdcall все параметры в стеке
Зарегистрируйтесь на Хабре, чтобы оставить комментарий