Хоооливар

Тема в разделе "LANGS.C", создана пользователем osox, 16 фев 2010.

  1. osox

    osox New Member

    Публикаций:
    0
    Регистрация:
    13 ноя 2009
    Сообщения:
    280
    Приветствую Всех заглянувших надеюсь вам будет интересно похоливарить ))) у меня есть вопрос к знатокам Си
    утром после бессонной ночи попивая кофе и затягиваясь смачно сигаретой я наткнулся на такую врезку из книги
    Искусство программирования на С авторы Ричард Хэзфилд Лоуренс Кирби
    предупреждаю первый сабж у меня хватило лени не компилировать порассуждаем логически
    (уже скомпилил) а вот второй сабж я скомпилил и обломался но обо всем по порядку и так врезка

    /*
    * Если уж мы говорим о sprintf может быть стоит заметить что следующая обычная конструкция
    * sprintf(mystring, "%s%d%s%f", mystring, j, otherstring, d);
    * приводит к неопределенному поведению программы поскольку компилятор
    * может проводить запись в mystring в таком порядке в каком хочет возможно начиная с конца выражения
    * а может сделать и по другому
    * Если вы дейсвительно хотите сделать это то в качестве области для временного хранения
    * используйте другую строку
    * sprintf(thirdstring, "%s%d%s%f", mystring, j, otherstring, d);
    * strcpy(mystring, thirdstring);
    */

    и как всегда на этом объяснения закончились какой бред подумал я начал и обдумывать
    что вообще он имел ввиду непонятно ясно что аргументы он вычислит рандомно в худшем случае но у нас в аргументах нет побочных эффектов на стеке (поcле двух обязательных аргументов) в кадре предыдущей функции первым будет копия указателя mystring выше копия j выше копия othersttring выше копия d
    я все пониманию вычисления рандомно аргументов может быть но все побочные эффеты заканчиваются при вызове
    функции ладно sprintf начнет парсить строку первый найдет %s и возмет из стека указатель на mystring потом на j
    и так далее где тут неопределенное поведение я не пониманию все законно несколько смущает
    что из mystring будет читатся байт и писаться сразу же в нее тоесть мы проосто получим копию mystring а остальные аргументы вообще в примере ни о чем не говорят
    что имел автор ввиду говоря эту фразу

    "поскольку компилятор
    может проводить запись в mystring в таком порядке в каком хочет возможно начиная с конца выражения
    а может сделать и по другому"

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

    /*
    * int main(void)
    * {
    * int dec = 0;
    * int a, b, c, d;
    *
    * show((++dec,a=dec), (++dec,b=dec), (++dec,c=dec), (++dec,d=dec));
    * }
    */

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

    00401030 >/$ 55 PUSH EBP /* сохраняем стэковый кадр предыдущей функции */
    00401031 |. 8BEC MOV EBP,ESP /* настраиваем на новый */
    00401033 |. 83EC 14 SUB ESP,14 *. ./* выделяем немного стека */
    00401036 |. C745 FC 000000>MOV DWORD PTR SS:[EBP-4],0 /* инициализируем dec *.
    0040103D |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] /*
    00401040 |. 83C0 01 ADD EAX,1 *
    00401043 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX *
    00401046 |. 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4] *
    00401049 |. 83C1 01 ADD ECX,1 *
    0040104C |. 894D FC MOV DWORD PTR SS:[EBP-4],ECX * прибавляем к dec 4
    0040104F |. 8B55 FC MOV EDX,DWORD PTR SS:[EBP-4] *
    00401052 |. 83C2 01 ADD EDX,1 *
    00401055 |. 8955 FC MOV DWORD PTR SS:[EBP-4],EDX *
    00401058 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] *
    0040105B |. 83C0 01 ADD EAX,1 *
    0040105E |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX */
    00401061 |. 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4]
    00401064 |. 894D F0 MOV DWORD PTR SS:[EBP-10],ECX /* инициализиуем a *.
    00401067 |. 8B55 FC MOV EDX,DWORD PTR SS:[EBP-4]
    0040106A |. 8955 EC MOV DWORD PTR SS:[EBP-14],EDX /* инициализируем b */
    0040106D |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
    00401070 |. 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX /* инициализируем c */
    00401073 |. 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4]
    00401076 |. 894D F8 MOV DWORD PTR SS:[EBP-8],ECX /* инициализируем d */
    00401079 |. 8B55 F0 MOV EDX,DWORD PTR SS:[EBP-10]
    0040107C |. 52 PUSH EDX ; /Arg4 /* пушим a */
    0040107D |. 8B45 EC MOV EAX,DWORD PTR SS:[EBP-14] ; |
    00401080 |. 50 PUSH EAX ; |Arg3 /* пушим b */
    00401081 |. 8B4D F4 MOV ECX,DWORD PTR SS:[EBP-C] ; |
    00401084 |. 51 PUSH ECX ; |Arg2 /* пушим c */
    00401085 |. 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8] ; |
    00401088 |. 52 PUSH EDX ; |Arg1 /* пушим d */
    00401089 |. E8 72FFFFFF CALL move.show ; \show
    0040108E |. 83C4 10 ADD ESP,10 /* прибавляем к указателю 16 байт занятые копиями локальных переменных */
    00401091 |. 33C0 XOR EAX,EAX /* устанавливаем код возврата функции */
    00401093 |. 8BE5 MOV ESP,EBP ./* настраиваем указатель что бы он указывал на адрес с адресом предыдущего кадра */
    00401095 |. 5D POP EBP /* восстанавливаем предыдущий кадр */
    00401096 \. C3 RETN ./* снимаем адрес возврата и выходим /*

    что мы видим ?

    приведу вызов функции чтоб не терять ниточки рассуждения
    show((++dec,a=dec), (++dec,b=dec), (++dec,c=dec), (++dec,d=dec));
    он сначало увеличил четыре раза значение dec потом скопировал 4 в каждый аргумент который и запушил в стэк теперь вопрос почему это так
    я себе видел это так выбирается рандомно(рандомно не в прямом смысле))) первичное выражение делается инкремент а потом присваивание и так для всех аргументов потому что запятая в первичном выражениии задает порядок жесткий на вычисление в итоге вызываемая функция должна была получить номера кто в каком порядке вычилялся а не четыре 4 но все с точностью до наоборот получилось
    он сначало выдрал из каждого выражения dec увеличив его на 4 а потом всем скопом присвоил значение 4
    вообщем загадка если есть люди знающие расскажите все подробно по первому и по второму сабжу чтоб заполнить этот пробел в знаниях тем кто дочитал до конца респект )))
     
  2. Ox8BFF55

    Ox8BFF55 New Member

    Публикаций:
    0
    Регистрация:
    11 июл 2009
    Сообщения:
    181
    Если логику не видно в OllyDbg то возми IDA pro...
     
  3. valterg

    valterg Active Member

    Публикаций:
    0
    Регистрация:
    19 авг 2004
    Сообщения:
    2.105
    Это не холивар, а детский сад. show и printf предназначены для вывода готовых переменных и никак для правильной работы в ситуациях, которые позволяет не в меру гибкий язык СИ :)
    Таких примеров полно, ну и что.
     
  4. osox

    osox New Member

    Публикаций:
    0
    Регистрация:
    13 ноя 2009
    Сообщения:
    280
    Moderated: Я очень плохой форумчанин и не знаю правил форума, извините, обещаюсь исправить.
     
  5. Rockphorr

    Rockphorr Well-Known Member

    Публикаций:
    0
    Регистрация:
    9 июн 2004
    Сообщения:
    2.622
    Адрес:
    Russia
    об этом даже крис писал в своих книгах
    если вы так пишете то ваша скомпилированная прога (компилятор здесь действительно не причем, если же в него не встроена проверка соответствия количества спецификаторов и количества параметров) по очереди вытаскивает из стека данные интерпретируя их в соответствии с указанными спецификаторами
    интрепретация эта равно как и вытаскивание из стека может нарваться на любые грабли в зависимости от реализации функции принтф
     
  6. Rockphorr

    Rockphorr Well-Known Member

    Публикаций:
    0
    Регистрация:
    9 июн 2004
    Сообщения:
    2.622
    Адрес:
    Russia
    я разбирал 16 разрядную функцию принтф джона соши - такого асемблерного бреда я не ожидал
     
  7. asmeradm

    asmeradm New Member

    Публикаций:
    0
    Регистрация:
    4 июл 2007
    Сообщения:
    26
    Тут кто-нибудь хоть знает что такое знаки препинания? :) Понять невозможно ничего...
     
  8. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    С чего ты взял, что именно в таком порядке будет парсить? Может в любом другом, это и неопределено ;)
     
  9. CyberManiac

    CyberManiac New Member

    Публикаций:
    0
    Регистрация:
    2 сен 2003
    Сообщения:
    2.473
    Адрес:
    Russia
    В зависимости от радиуса кривизны системной библиотеки на таком sprintf можно вообще получить разрушение стека, если в mystring будет содержаться хоть один '%s'. [От души приправленный ядом сарказм] sprinf и ей подобное определённо придумал очень умный человек, который желал миру добра и процветания.
     
  10. CyberManiac

    CyberManiac New Member

    Публикаций:
    0
    Регистрация:
    2 сен 2003
    Сообщения:
    2.473
    Адрес:
    Russia
    Хотя для самого веселья лучше делать что-нибудь типа sprintf(my_fucking_string,another_fucking_string,another_fucking_string,...)

    Интересно, возможен ли вообще достаточно мощный язык, при этом не допускающий заведомо идиотские конструкции?
     
  11. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    Достаточно мощный следует понимать как Тьюринг-полный? Такие языки есть, pure functional
     
  12. osox

    osox New Member

    Публикаций:
    0
    Регистрация:
    13 ноя 2009
    Сообщения:
    280
    теоретически возможно что такой вызов
    sprintf("%s %d %s %d", arg1, arg2, arg3, arg4);
    она начнет парсить строку спецификаторов с конца
    динамически перераспределяя память и записывая выходную строку с конца но
    тогда ей придется сдвигать уже отформатированную строку вправо
    каждый раз для вставки нового фрагмента какой в этом смысл
    если можно например перераспределяя каждый раз в два раза больше памяти добавлять фрагменты в конец по порядку лишних перемещений в памяти не будет но да возможно и с конца начинать парсить
    но я не вижу решения без перемещений уже готовой строки вправо при вставке нового фрагмента
    если конечно не представлять строку связным списком один символ один узел и вставлять постоянно в голову списка
    новый фрагмент тогда да перемещений не будет но и оверхед не малый получится да и потом чтоб ее скормить
    WriteConsole придется еще перегнать список-строку в непрерывный блок памяти ;)
     
  13. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
  14. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    Теоретически возможно что угодно, библиотека рассчитывает что поинтер на буфер не имеет алиасов
    int sprintf(char * restrict s, const char * restrict format, ...);
     
  15. CyberManiac

    CyberManiac New Member

    Публикаций:
    0
    Регистрация:
    2 сен 2003
    Сообщения:
    2.473
    Адрес:
    Russia
    J0E
    Нет, "достаточно мощный" в смысле "на нём можно написать хотя бы Калькулятор под винду и не состариться за это время". Ессно, модные "биндинги к фреймворкам" маршируют лесом - предполагается наличие только операционки, языка и родных им библиотек.
     
  16. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    Что такое калькулятор, ГУЙ или _автоматически_ с генерированный парсер выражений?
     
  17. CyberManiac

    CyberManiac New Member

    Публикаций:
    0
    Регистрация:
    2 сен 2003
    Сообщения:
    2.473
    Адрес:
    Russia
    J0E
    ГУЙ конечно. Конечному пользователю традиционной ориентации голый парсер не нужен. А программы обычно пишутся для конечного пользователя.
     
  18. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    Гы. рассчитываешь на проблемы биндинга с голым ВинАПИ? В этом нет сложностей, вопрос зачем оно надо и какое отношение имеет к проблеме sprintf. Тролль, но расскажу и другие варианты:
    * взять HTTP сервер из родной библитеки и реализовать ГУЙ в браузере.
    * писать на С в pure functional стиле.
    * использовать саму среду языка как мощный калькулятор и совсем ниче не писать :)
     
  19. CyberManiac

    CyberManiac New Member

    Публикаций:
    0
    Регистрация:
    2 сен 2003
    Сообщения:
    2.473
    Адрес:
    Russia
    J0E
    Нет, на то, что традиционные языки с WinAPI (особливо там, где нужно сочинять callback-функции) работают без танцев с бубном.

    На этой планете есть хоть один язык программирования (а не среда разработки), которому в стандартную библиотеку положен HTTP-сервер? А из европейской винды, кстати, уже и браузер вынули. Хотя в Delphi вроде был какой-то компонент... Значит, всё-таки один есть, и то с оговорками - этот компонент там не во всех редакциях.

    "Среда языка" - это, наверное, про Visual Basic. Более традиционные языки не требуют обязательного наличия какой-либо IDE для своей работы. Да и конечному пользователю этот "калькулятор" вручать вместе со средой, лицензией на её использование и подробной инструкцией, куда там нажать, чтобы сложить 2 и 2, как-то не комильфо.
     
  20. J0E

    J0E New Member

    Публикаций:
    0
    Регистрация:
    28 июл 2008
    Сообщения:
    621
    Адрес:
    Panama
    CyberManiac, читай историю. Тьюринг формализовал понятие алгоритма в 1936 году, в этом же году Черч предложил лямбда исчисление, можно сказать самый первый язык программирования. Потом лямблу реализовали в языке Лисп. То что ты называешь традиционными языками появилось позже, твои намёки неуместны и показывают твое зацикливание на ориентации ;) И конечно же ВБ не является функциональным языком, из функциональных языков IDE есть разве что у F# и я имел ввиду не это, если есть боязнь смотреть на среды ФЯ то возьми самый ассемблерный язык forth.