Аннотация
Во второй том книги «Программирование: введение в профессию» вошли её третья и четвёртая части.
Третья часть книги посвящена программированию на уровне машинных команд на примере ассемблера NASM. Рассматривается «юзерспейсовская» часть системы команд i386, конвенции системных вызовов Linux/i386 и FreeBSD/i386, изучается макропроцессор, раздельная трансляция и работа компоновщика, приведены сведения об арифметике с плавающей точкой.
Четвёртая часть, посвящённая языку Си, включает, кроме собственно описания этого языка, также краткие сведения о библиотеке ncurses; рассказ о том, как использовать компилятор Си без его стандартной библиотеки; дополнительные сведения об инструментах сборки и отладки программ; наконец, в книге приводится краткое описание систем контроля версий CVS и git.
Публикация в бумажном варианте
Опубликовано издательством МАКС Пресс (Москва) в 2016 году. ISBN 978-5-317-05301-7.
Электронная версия
Электронная версия, идентичная печатному изданию, доступна здесь: http://www.stolyarov.info/books/pdf/progintro_vol2.pdf
Статус бумажной версии
Книга распространялась среди донэйторов проекта и поступала в свободную продажу. К настоящему моменту распродана.
Архив примеров программ
Архив, содержащий примеры программ из первого и второго тома, можно скачать здесь: http://www.stolyarov.info/books/extra/progintro_vol1_2_examples.tgz
Напоминаем, что раскрыть этот архив можно командой
tar -xzf progintro_vol1_2_examples.tgz
Обновлённую версию файла stud_io.inc
можно взять здесь: http://www.stolyarov.info/books/extra/stud_io_inc Не забудьте переименовать файл! Это делается так:
mv stud_io_inc stud_io.inc
c
Здравствуйте Андрей Викторович!!! Очень Вам признателен за такой ТИТАНИЧЕСКИЙ ТРУД!!!
у меня вопрос по С.
на странице 202 есть строка как компилировать и линковать библиотеку.
gcc -Wall -g -lm qe.c -o qe
у меня такая запись не работает, сработала только когда я -lm поставил после qe.c.
Так вот, вопросик, в чем может быть в данном случае проблема? это у меня одного так криво работает?
Обсуждалось же
Обсуждалось же уже, причём на этой же странице: http://www.stolyarov.info/books/programming_intro/vol2#comment-1260
Вкратце: gcc версий до 4.* включительно позволяли библиотеки подключать в любом месте командной строки, начиная с 5.* позволять перестали, и это логично (см. принципы работы редактора связей, описанные в части про ассемблер).
А книга уже вообще-то переиздана, и во втором издании сей момент не только исправлен, но и вставлен комментарий на эту тему.
Благодарю за
Благодарю за ответ, потерялся я слегка, между изданиями=)
Вопрос закрыт=)
Побитовые операции
Добрый день, Андрей Викторович! Подскажите, пожалуйста, для чего в операциях внесения/удаления элемента в массив (Том 2, стр. 75, 1-е изд.) используются операция "and cl, 11111b", если до этого мы в BL уже имеем нужную позицию (число не превосходящее 32) с помощью:
shl ebx, 3
shr bl, 3
Заранее спасибо!
В тексте
В тексте написано, что можно сделать или так, или так — не вижу, чтобы хоть в одном месте было и то, и другое. Или я чего-то таки не вижу своим замыленным взглядом?
Все верно, это я
Все верно, это я смешал все в кучу, прошу прощения
Может я ошибаюсь.
Андрей Викторович, добрый день! На странице 286 в функции дважды выполняется присваивание переменной lnum единицы, случаем не опечатка? И почему присваиваем именно единицу, по итогу у нас выдается в стандартный вывод число на единицу больше, чем фактически прочитано строк.
Строки в
Строки в текстовых файлах традиционно нумеруются с единицы — например, при выдаче диагностики всякими компиляторами. Работать вроде должно корректно, там же в условии неравенство нестрогое, а в стандартный вывод там символы выдаются (первые 10 строк), а само это число не выдаётся вроде.
По поводу присваивания дважды — вы абсолютно правы.
К счастью, во втором издании этот пример переработан, лишнее присваивание выброшено, заодно заголовок цикла упрощён, а переменная переименована.
Здравствуйте,
Здравствуйте, Андрей Викторович! Если ещё есть время, проверьте возможные опечатки
1т.
с.90 - h, j, k, l набраны обычным шрифтом
3т.
с.391 - "контроллера DMA" вместо "контроллер DMA"
4т.
с.202 - "источника (например, прочитать из файла) либо если" запятая после закрывающейся скобки
Круто, все три --
Круто, все три -- в точку. Спасибо!
Подскажите, пожалуйста, что я упустил...
Читаю второй том и до сих пор было ощущение, что понимаю вообще все. Но вот в главе про динамическую память в Си встретил пример
Смотрю как баран на новые ворота. Что означает
(double)i
? Или что я пропустил и не усвоил?Это операция
Это операция преобразования типа выражения, см. стр. 233.
Слишком сложный .bss
На стр. 47 вы даете пример, если написать:
db "This is a string", то размер исполняемого файла увеличится на 16 байт, а если написать resb 16 то ассемблер выделит 16 байт, но размер исполняемого файла не увеличится.
Вопрос следующий, если есть информация в исполняемом файле о резервировании какой-то области памяти, то как инструкция которая за это отвечает может иметь нулевой размер(как она может быть вообще прочитана) ?
Что будет если использовать каким-то образом эти 16 байт(секции .bss), файл будет увеличиваться в размере во время исполнения ?
Ответ на ваш
Ответ на ваш вопрос содержится на стр.44, верхний абзац. На всякий случай: обсуждаемый "размер секции .BSS" -- это 32-битное целое беззнаковое, и совершенно пофигу, чему оно равно, нулю или миллиарду, 32 бита и есть 32 бита. Ну, в 64-битном случае будет 64 бита, но от размера секции BSS в любом случае никак не зависит размер числа, в котором этот размер секции записан.
Банальный (наверное) вопрос
Недавно читал параграф, посвящённый макровызовам (стр 110), в котором приводятся примеры в духе:
И предлагается в качестве упражнения описать аналогичные макросы pcall4, pcall5, ... pcall8.
После чего задаётся вопрос: "почему стоит остановиться именно на описании pcall8 ?".
Первое, о чём я подумал - поддержание стека кратным 4-ём, но сразу отмёл это, потому как все параметры заносятся в него в виде dword, и очищается стек затем точно так же (суммарно уходит столько же байт, сколько пришло).
В итоге у меня остался только вариант "количество параметров не должно превышать 32 байта", но почему это так, мне не совсем понятно, можно какой-нибудь намёк на истину? =)
Всё намного проще
Эти вот %1, %2 и т.д. — в NASMе предполагают всего одну цифру. То есть максимальный параметр макроса, до которого можно дотянуться без команды rotate — девятый. Первый у нас занят под имя подпрограммы, на параметры остаётся восемь.
Почему два байта, а не четыре?
На стр. 48 предоставлены две команды:
mov eax, ebx
mov eax, 5
На стр. 49 Вы рассказываете, что первая команда будет занимать в памяти два байта, почему два? Мы копируем регистр EBX[а он у нас 32-битный в регистр EAX(так же 32-битный)], но(mov eax, 5) как пишут разные источники под непосредственный операнд выделяется 32 бита, следовательно, 32 бита пересылаются в 32-битный регистр, почему пять байт ?
Не могли бы вы, Андрей Викторович, дать пояснение почему в памяти будут два и пять байт в первом и втором случае.
Да-с...
Для начала — разрядность регистра тут ни при чём от слова совсем, то есть вот вообще, то есть никак. Честно говоря, не могу себе представить, из-за чего в голову могло прийти, что размер кода команды как-то связан с разрядностью операндов (и как именно).
Код команды
mov eax, ebx
будет89D8
, причём на самом деле здесь код команды — только первый байт (89), а откуда получилось D8 — подробно разобрано в книге, см. первый том, стр. 189 (если кратко, там два старших бита означают, что оба операнда регистровые, а младшие шесть — номера самих регистров).Код команды
mov eax, 5
будетB805000000
, здесь, формально говоря, код команды — это первые пять бит из байта B8, в том же байте младшие три бита обозначают регистр eax, а вот это вот05000000
— тот самый непосредственный операнд из пяти байт.А вот вопрос "почему коды именно такие" не имеет иного ответа, нежели "так придумали создатели процессора".
Определение DB
На стр. 46 там есть пример:
"welmsg db 'Welcome to Cyberspace!'".
Появляется следующий вопрос, как данная строка может влезть в Define Byte, если выделяется всего 1 байт, а строка состоит из 22 символов(соответственно, 22 * 8 = 176 Б) ?
Можете считать,
Можете считать, что это не "define byte", а "define bytes", если вам так удобнее. И db/dw/dd, и resb/resw/resd предполагают выделение не одного элемента указанного типа, а какого-то их количества (в d? количество определяется инициализатором, в res? указывается явно).
imul - idiv
Здравствуйте Андрей Викторович!
На стр. 61 есть предложение "Знак остатка, вычисляемого командой imul, всегда совпадает ...". Может быть idiv?
Конечно
Эту поймали уже, но всё равно спасибо за внимательность :-)
Опечатки
Здравствуйте, Андрей Викторович! Прошу проверить возможные опечатки опечатки
1 том
с. 73 - возможно стоит уточнить с какими правами программы откажутся запускаться
с. 232 - ошибка в блок-схеме
с. 256-257 - "3 8 7 5" вместо "3 8 5 7" и "5 7 8 3" вместо "7 5 8 3"
с. 375 - возможно лучше "при удалении первого или любого другого элемента"
2 том
с. 253 - NULL (шрифт)
с. 301 - "для кого либо, кроме владельца"
часть 4, с. ??? - "срочки" вместо "строчки"
с. 336 - от 0 до 16 вместо от 0 до 15
с. 348 - 3-ий вариант MYMACRO должен откомпилироваться
3 том
с. 43 - i-node (шрифт)
с. 116 - а [роль] сигналов в жизни вашей программы
с. 146 - процесс может уже быть
с. 207 - других типов файла
с. 227 - объявление res после операторов
Там же - if(res < 1) { /**/ continue;} if(res == 0) { /* никогда не выполнится */ }
Там же - не используется writefds
4 том
с. 59 - reverse_list_to
с. 60 - res в reverse_list
с. 74 - форматная строка
с. 288 - прЕдуманный
с. 293 - )»)
с. 311 - а те, что спецификации соответствовать
с. 339 - седьмой?
с. 415 - isomorpic
с. 490 - vi или vim?
Введение в Си++ (возможно есть в 4-ом томе)
с. 39 - операторы циклов (for, if, while)
PS: капча настолько жуткая, что аж спать захотелось
re: Опечатки
> с. 73 - возможно стоит уточнить с какими правами программы откажутся запускаться
Честно говоря, не понял, чего вы тут хотите. Есть программы (в основном графические), которые проверяют свой euid, и если он нулевой, отказываются стартовать — заявляют, что их нельзя запускать под root'ом. Именно это в тексте и написано.
> с. 232 - ошибка в блок-схеме
А в чём состоит ошибка?
> с. 256-257
есть такое, уже нашли
> с. 375 - возможно лучше
согласен, фраза корявая. Подумаю, как её переформулировать.
> с. 253 - NULL (шрифт)
уже поправлено
> с. 301 - "для кого либо
с этим вам сюда :-)
> "срочки"
уже вывловлено
> с. 336 - от 0 до 16 вместо от 0 до 15
тоже уже выловлено
> с. 348 - 3-ий вариант MYMACRO должен откомпилироваться
Да неужели?! Сами поймёте, почему не компилируется, или объяснить?
> с. 43 - i-node (шрифт)
> а [роль] сигналов
> с. 146 - процесс может
уже поймали
> с. 207 - других типов файла
пожалуй, тут фраза построена криво, переделаю
> с. 227 - объявление res после операторов
> Там же - if(res < 1) { /**/ continue;} if(res == 0) { /* никогда не выполнится */ }
> Там же - не используется writefds
Вот тут вы абсолютно правы по всем пунктам, большое спасибо! Исправлено.
> с. 59 - reverse_list_to
> с. 60 - res в reverse_list
уже выловлено
> с. 74 - форматная строка
Ого! Что значит замыленный взгляд — я этот фрагмент ко второму изданию правил в хвост и в гриву, но вот этого косяка так и не заметил. Спасибо!
> с. 288 - прЕдуманный
факт
> с. 293 - )»)
Уже поймали
> с. 311 - а те, что спецификации соответствовать
Бррр, а где ошибка-то?
> с. 339 - седьмой?
поправил на "с индексом 7", чтобы исключить разночтение
> с. 415 - isomorpic
факт
> с. 490 - vi или vim?
Конечно, vi. Я подозреваю, что vim'а ещё не было, когда этот интерпретатор писали :-) но дело даже не в этом, vim всё-таки ставить надо, а vi, по идее, должен присутствовать в любой *nix-системе (хотя в наше дебильное время это уже не так).
> операторы циклов (for, if, while)
И ведь тоже факт, да.
В общем, спасибо огромное, целая пачка косяков не попала во второе издание.
> PS: капча настолько жуткая, что аж спать захотелось
Увы, это экспериментально подобранный уровень сложности, ниже которого спам пролезает тоннами, рука устаёт его вытирать.
Во второй
Во второй блок-схеме последующие итерации цикла осуществляются при ложном условии (yes и no местами перепутаны).
Ума не приложу, как я мог в "кого-либо" дефис пропустить, но я имел в виду запятую перед "кроме".
На счет MYMACRO: решил перепечатать Ваш пример, и все сразу понял :) Не учел, что он вызывается в операторе if, у которого есть else. Заодно задумался, насколько же Си все-таки кривой язык.
По поводу 4 т. с 311.: перечитал и понял, что это уже я ошибся (подумал, что слово пропущено).
vi: Сейчас попробовал дать команду vi, а в итоге открылся vim O_O
PS: Сайт, на который Вы дали ссылку, не работает с отключенным js.
> Во второй
> Во второй блок-схеме последующие итерации цикла осуществляются при ложном условии
В Паскале именно так и происходит, это же не Си. После слова until записывается условие выхода из цикла (а не продолжения цикла, как в сишном do-while).
> я имел в виду запятую перед "кроме".
А, ну вообще да, пожалуй, тут она нужна. Это на меня в очередной раз подействовали отголоски английской пунктуации :)
> насколько же Си все-таки кривой язык.
Не, ну он кривой, конечно, но я бы не сказал, что вот прямо здесь.
> vi: Сейчас попробовал дать команду vi, а в итоге открылся vim
Ну кто-то же должен "быть за него" :) Больше того, vim имеет специальный режим совместимости, в котором работать с непривычки несколько тяжко.
> PS: Сайт, на который Вы дали ссылку, не работает с отключенным js.
Чёрт, это косяк, да, буду внимательнее.
Опечатка? Стр.
Опечатка? Стр. 159, 1-й том:
"... пока результат оДного не потребуется для другого вычисления."
Нет здесь
Нет здесь никакой опечатки.
Мда. Вот и выросло поколение, не знающее слова "оный".
Исполнительный адрес
Здравствуйте Андрей Викторович!
На странице 53 вы пишите, что в исполнительном адресе нельзя использовать три регистра и ещё целый ряд ограничений. Понятно, что выражение для вычисления исполнительного адреса не может быть произвольным. Неочевидно из каких соображений выводятся ограничения. Не могли бы вы подробнее рассказать об этом?
Нет, не мог бы.
Нет, не мог бы. В основном это вопрос не ко мне, а к создателям процессора. В документации обычно отражается то, КАК устроены команды, но никто не рассказывает о том, ПОЧЕМУ всё сделано именно так, можно лишь догадываться.
Ну, про три регистра более-менее понятно: трёхмерные массивы встречаются крайне редко, а разрядность машинного кода (в котором исполнительный адрес должен быть закодирован весь целиком) не безгранична. Но вообще — нет, подоробнее обо всём этом могли бы рассказать разве что инженеры-схемотехники, проектировавшие процессоры в фирме Intel в 1980-е годы.
На стр.144В этом
На стр.144
В этом примере мы не обрабатываем ошибки, предполагая, что запись в стандартный поток ввода всегда успешна
Не должен ли быть тут поток вывода?
Абсолютно
Абсолютно верно, и таки да, до сих пор никто не заметил. Спасибо!
Сколько ж там ещё таких "жуков", а?...
Опечатка
Известно ли уже про опечатку на стр. 74 второго тома?
потом в EDX мы выполним сдвиг вправо и реультат, который полностью уместится..
Спасибо!
Самое забавное, что нет — до сей поры не заметил её никто. Ещё бы чуть-чуть, и быть ей во втором издании.
Тогда
Тогда проверьте еще стр. 80. Не закрыта открывающая скобка.
...например cli и sti для IF(interrupt flag, но воспользоваться ими в ограниченном режиме нельзя.
Эту уже
Эту уже заметили :-) но всё равно спасибо.
функция open
Андрей Викторович, а можете объяснить, как реализована функция open (вернее, ее вариант с третьим аргументом perms). Судя по заголовку, она не вариадическая? Как же тогда, если в Си нет перегрузки функций?
Сами в
Сами в заголовочник заглянуть никак не можете? Там (в /usr/include/fcntl.h) функция open объявлена именно что как вариадическая, два аргумента заданы, вместо третьего многоточие. Естественно, в Си иначе и не получится.
Здравствуйте.
Здравствуйте. Небольшое замечание для страницы 439. Сейчас, если создавать 32-битный бинарь на 64-битной архитектуре, требуется передать -m32 в команду gcc, а также передать -m elf_i386 для ld.
У меня сработало в таком виде:
nasm -f elf start.asm // тут как раз флаг -f указывает на создание 32-битного
nasm -f elf calls.asm
gcc -m32 -Wall -c greet3.c // если не указать -m, то будет 64-битный. И тогда линкер будет ругаться на несовместимость типов объектных файлов
ld -m elf_i386 start.o calls.o greet3.o -o greet // здесь тоже для указания именно 32-битного
2 с лишним дня убил на эти ошибки. Кому-то да пригодится. И, если честно, что-то сейчас никакой экономии в размере толком не увидел почему-то, без символов получилось 13Kb. Как получилось меньше килобайта в книге - без понятия)
Про -m32 да, в
Про -m32 да, в исходнике уже добавлено, ко второму изданию всё будет. Про размер ничего сказать не могу, крайне странно — не должно такого быть.
Тоже долго
Тоже долго мучился, пытаясь понять, почему получается такой большой бинарник.
Проблема решилась (по крайней мере, я надеюсь, что это решение хоть на что-то годится) добавлением флага
--omagic
для редактора связей.Собирал программу так:
Итоговый бинарник "похудел" с 12720 байт до 1088. Еще немного сбросит (до 922), если добавить для
gcc
флаг-O2
(как-то это странно).При наличии аргумента командной строки программа выполнила 4 системных вызова, без него - 2.
------------------------------
Андрей Викторович, отдельно хотелось бы обратить Ваше внимание на некоторые моменты в 4-ом томе:
с. 174 - использование одного и того же кода для разных ошибок
c. 224 - "make_il" вместо "make_el" (опечатка?)
Также интересует принципы оформления ссылок, так как в книге не видно единообразия (например,
int&
и&Get()
). И еще хочется узнать, планируется ли в новом издании глава про оформление кода на Прологе (она, вроде, не такая большая).> с. 174 -
> с. 174 - использование одного и того же кода для разных ошибок
Вообще обычно так и делают, 0 -- успех, 1 -- неуспех. Но вы правы, надо как-то соблюдать единообразие, а у меня как раз в других местах коды разные. Поправил.
> c. 224 - "make_il" вместо "make_el"
Где вы там вообще
_el
нашли? Везде_il
, в том числе в имениprint_il
. Имелость в виду int list.> (например, int& и &Get())
Я старался придерживаться простого принципа: когда есть что-то справа, то звёздочка и амперсанд прилипают вправо, когда справа ничего нет — например, надо просто сказать, что кто-то там имеет тип ссылка на int, не показывая этого кого-то — то прилипает влево.
> И еще хочется узнать, планируется ли в новом издании глава про оформление кода на Прологе (она, вроде, не такая большая).
Кстати, да, это мысль.
А можно узнать
А можно узнать версии gcc, ld и nasm? Про остальное завтра посмотрю, сейчас некогда, сорри.
Чёрт, рано
Чёрт, рано радовался. Такое "решение" хуже самой страшной проблемы. Оказывается, ради экономии памяти этот
--omagic
заставляет редактор связей объединить секции.data
и.text
в одну секцию.text
, содержимое которой делает доступным и для записи, и для исполнения.readelf
это только подтвердил.Какой ужас.
gcc - 9.3.0nasm -
gcc - 9.3.0
nasm - 2.14.02
ld - 2.34
Ага, вот тут собака и порылась
Вот кто-нибудь ещё после этого верит в существование какого-то там "технического прогресса" в области IT?
Здравствуйте.
Здравствуйте. Возможно, опечатка на странице 284.
> "в зависимости от значения perror"
Разве не "значения errno"?
Уже поправлено,
Уже поправлено, но всё равно спасибо :-)
Возможные опечатки в I томе
Доброго дня, Андрей Викторович!
Прошу проверить возможные опечатки:
с.53
соответстветствующее
c.70
PC-BSD (на википедии "TrueOS (formerly PC-BSD or PCBSD)")
c.128
217A -> 217B
с.197
writeln('Hello, world') -> writeln('Hello, world!')
с.216
quad -> square
с.243
var i: integer; (без перевода строки)
с.264
end -> end;
с.292
3. ... кроме набольшего ...
с.299
for i := 2 to MaxSchool do
с.300
for i := 1 to MaxSchool do
с.340
halt -> halt(1)
с.360
символом "?!" -> символами "?!"
с.412
";" перед end
var p, q: ... (нет перевода строки)
c.413
";" перед end
Возможные неточности в архиве:
";" перед end:
colordemo.pas
ст.42
diamond.pas
ст.19,21,33,35
diamondp.pas
ст.18,20,34
frcancel.pas
ст.17
hanoi2.pas
ст.36,43,110
hanoi3.pas
ст.11,70,72,89
hanoi.pas
ст.26
hello20.pas
ст.9
message_n.pas
ст.9
movingstar.pas
ст.40,55,75
olympcount.pas
ст.28
skip_indented.pas
ст.22
star_slash.pas
ст.9
лишние пробелы после строки:
gennubin.pas, gennumtx.pas
ст.21
hanoi.pas
ст.26
hanoi2.pas
ст.110
hanoi3.pas
ст.89
Большое спасибо за книги!
Спасибо!
> соответстветствующее
Самое интересное, что таки да — ни корректор не выловил, ни публика за скоро пять лет как, ни я сам при повторном вычитывании. Спасибо, поправил.
> TrueOS
Её переименовали позднее, чем первый том вышел. Там более интересная информация есть — авторы свой проект, оказывается, в этом году бросили. Видимо, просто выкину упоминание.
> 217A -> 217B
Уже известна, следующие три тоже, но всё равно спасибо за внимательность :-)
> var i: integer; (без перевода строки)
В принципе это допустимо, но Вы, пожалуй, правы, единый стиль лучше поддерживать везде.
следующие две известны
> for i := 1 to MaxSchool do
Как говорят, good catch. Спасибо, поправил.
> halt -> halt(1)
Пожалуй, согласен. Поправил.
> с.412
Уже :-)
> ";" перед end:
Часть было уже поправлено, но примерно две трети — новых. Огромное спасибо.
> лишние пробелы после строки:
Прошёл в итоге grep'ом по всем файлами примеров, вычистил это дело (в примерах к другим частям книги этого добра оказалось ещё больше).
Спасибо за пост, вы мне очень помогли!
А известно ли
А известно ли про опечатку на ст. 92?
Права на чтение соответствуют 1, ...
1 в восьмеричной это 001 в двоичной системе и он должен соответстовать правам на исполнение? Хотя дальше, в тексте, 1 используется для обозначения прав на исполнение.
Спасибо.
Эту почему-то
Эту почему-то всегда находят первой :-)
Здравствуйте,
Здравствуйте, страница 79. Несколько вопросов-замечаний по примеру с дополнением строки "This is a string" подстрокой "long ".
Во-первых, buf2 в объявлении секции .data должен идти раньше buf1, в противном случае когда мы выполним
mov edi, buf1+17+5
мы перезапишем buf2. То есть должно быть так:buf2 db "long "
buf1 db "This is..."
-----
Во-вторых, непонятен момент когда опять же мы сдвигаем buf1 на 22 байта во второй строке. Учитывая, что buf1 будет выше по стеку, чем buf2, теоретически мы можем попасть в область памяти, которая не принадлежит нашему процессу? В таком случае надо объявлять дополнительный буфер поверх buf2? К примеру
buf2 db "long "
buf1 db "This is..."
buf3 db " " ; много символов пробела
Тогда при сдвигании мы попадем в область дополнительного буфера, а это уже точно наша память.
-----
В-третьих, зачем сдвигать на 8 байтов во 2ой строке, когда нам нужно сдвинуть слово "string", а оно длиной в 6 символов.Тогда код со 2ой по 4ую строку преобразуется
mov edi, buf1+20
mov esi, buf1+15
mov ecx, 6
-----
Поправьте, если где-то ошибся. Спасибо.
Эк
> в противном случае когда мы выполним mov edi, buf1+17+5 мы перезапишем buf2
А ничего, что они там (в самом верху той же страницы) описаны каждый по 1024 байта?
> Учитывая, что buf1 будет выше по стеку, чем buf2
По какому ещё стеку, простите? В этом примере никакой стек не используется.
> сдвигать на 8 байтов во 2ой строке
Там сдвигается не "на" восемь байтов, там восемь -- это количество байтов, которые копируются. Но вообще да, восемь тут многовато будет :-) Да и 17, кстати, тоже.
Не обратил
Не обратил внимания что сверху объявляется по 1024 байта. Я как раз и столкнулся с проблемой, когда объявил строки в секции .data, а не в .bss.
И стек там тоже ни при чем. Думал об одном, написал о другом. Просто если объявлять строки в .data, то порядок того, как они будут лежать в памяти с точностью до наоборот как они будут объявлены, поэтому там было важно чтоб они не затерли друг друга.
Первые 2 пункта не актуальны :)
если объявлять
если объявлять строки в .data, то порядок того, как они будут лежать в памяти с точностью до наоборот
Это ещё с какого бодуна? Вас кто-то обманул:
Действительно,
Действительно, значит где-то я ошибся. Буду разбираться. Спасибо за ответ!
Опечатка?
На с. 45:
... в четвёртое - число 5 и т.д.
Это, вроде бы, опечатка, там должно быть число 3? В старой версии книги тоже самое.
Есть такая, уже
Есть такая, уже нашли. Спасибо за внимательность :)
Фрагментация динамической памяти
Здравствуйте, Андрей Викторович.
Дочитав второй том вашей книги, я не заметил освещения этого важного вопроса. Сейчас пробежался глазами по главам и конспекту и ничего не могу найти. Не обессудьте, если я просмотрел, поиск по вашему учебнику не работает, - слой вы убрали. Многие важные темы ведующие к серьёзным потенциальным ошибкам, вы очень ярко освещали: про функцию gets, про утечку памяти через потерянные указатели и т.д. Но вот про правило стека, при выделении и освобождении динамической памяти, я нигде не помню. Может это есть, но как-то вскользь.
В книге этого
В книге этого нет. Насколько я понимаю, при использовании современных (ну как современных... лет за 25 последних) версий malloc/free правило стека неактуально, поскольку от хранения свободных блоков в виде списка давно уже отказались, сейчас там что-то вроде бинарного дерева, в котором сиблинги автоматически сливаются.
А что
А что подразумевается под правилом стека?
Освобождать
Освобождать фрагменты динамической памяти в порядке, обратном к тому, в котором они были выделены. Естественно, по возможности, т.к. куча всё-таки не стек.
Проблема всё таки актуальна в наши дни.
Я так понял, это проблема существует и поныне. И если не придерживаться некоторых правил, то можно быстро дыр в памяти наделать. Вот тут описываются рекомендации https://cpp4arduino.com/2018/11/06/what-is-heap-fragmentation.html .
В любом случае, Андрей Викторович, на протяжении чтения всего второго тома, этот вопрос мне не давал покоя. Может его как-то стоит осветить в книге?
Вы таки издеваетесь? :)
Сослаться на сайт, где люди на полном серьёзе применяют Си++ (!) на микроконтроллерах (!) и на этом основании говорить, что проблема актуальна — это, я бы сказал, эпично.
NB: когда вам в следующий раз предложат использовать Си++ на микроконтроллере, или даже когда вам в следующий раз скажут, что на микроконтроллере может быть менеджер кучи (пусть даже на чистом Си), вы от этих людей держитесь подальше на всякий случай. Вдруг буйные окажутся.
UPD: а в статье не просто Си++, там C++17. Точно буйные.
Дак ведь нам,
Дак ведь нам, студентам, читающим ваши книги, ещё неведомы эти хитросплетения: "плюсы-не плюсы". Важно, что вопрос по хранению и управлению блоками динамической памяти, возникает в голове сразу после того, как вы вводите этот материал. К тому же, вы учите не самому Си, а пониманию, как там-всё это за кадром работает. Вы хотя бы успокойте где-нибудь, мол управление через умную кучу и всё инкапсулировано, и нечего лезть, куда тебя не просят; всё будет работать, выделяй-освобождай, Вася, как душе угодно.
А ссылку я вам скинул именно эту, потому, что там всё оформлено красиво и по полочкам. Подобные темы и на хабре и на rsdn обсуждаются.
http://rsdn.org/forum/cpp/908825.flat
Во-первых,
Во-первых, дискуссия, на которую вы ссылку дали, состоялась в 2004 году и там вспоминают (в прошедшем времени), что где-то что-то когда-то было.
Во-вторых, там же к концу обсуждается и то, что в менеджере кучи обычно реализован метод близнецов и метод Кнута (про последний, кстати, я забыл, а он есть). И говорится, что абсолютной гарантии они не дают. Это действительно так. Проблема в том, что это ваше "правило стека" тоже не может ничего гарантировать, а будучи применённым в куче, которая реализована хоть чуточку сложнее, чем список свободных блоков, правило стека не даст вообще ничего, кроме головной боли.
А в-третьих, и в-главных, я как-то надеялся, что те, кто читают мои книги, уж точно плюсы с чистым Си путать не будут.
Здравствуйте,
Здравствуйте, Андрей Викторович.
Во втором томе, на странице 402:
Отметим, что двух идентификаторов в описании переменной быть не может, поэтому параметр функции, который в описани указателя на эту функцию должен быть безымянным....
а полностью описание указателя на функцию replace_f(или другую с тем же профилем) окажется таким:
int(*(*replace_f_ptr(int(*)(int,int)))(int,int);
Как читателю, этот момент с безымянным указателем мне показался непонятным. Почему непременно должно быть такое описание, а не int(*(*replace_f_ptr(int(*fun)(int,int)))(int,int); ? Я так понял, вы имели ввиду, что бессмысленно указывать второй идентификатор, а не то, что это приведёт к ошибке компиляции. Но если я всё-таки всё правильно понял, то идентификатор с точки зрения лучшей читаемости кода можно было оставить.
https://stackoverflow.com/questions/64874499/complex-type-declaration-wi...
Ну да, есть такое
Когда-то давно — это я точно помню, мне тогда ещё двадцати не было, а в том возрасте события запоминаются ярко и подробно — я столкнулся с компиляторами, запрещавшими больше одного идентификатора в любом описании/объявлении, за исключением заголовков функций (в смысле настоящих заголовков функций, не являющихся частями описания переменных или типов). Сейчас проверил — компилятор жрёт больше одного идентификатора даже в режиме -ansi -pedantic; но тот компилятор, про который я рассказываю, мне попался году этак в 1993, и он, скорее всего, относился к эпохе до пришествия стандартизаторов.
Так что текст-то я в книжке поправлю, благо там буквально пару слов поменять. Но вот добавят ли лишние идентификаторы ясности — это, знаете ли, вопрос. Вот вам для примера:
Эта штука всё ещё компилируется.
Я это к тому, что, ежели вы озабочены ясностью, то надо не идентификаторы лишние внутрь описания запихивать, а использовать typedef, как это предлагается в следующем абзаце в тексте книги. И то, что вы таки засунете туда func, никак не отменит того факта, что при чтении чьих-то ещё программ вы рано или поздно столкнётесь со звёздочкой в скобках, и к этому лучше всё же быть морально готовым (хотя самому такого писать, наверное, не стоит).
А, ну и ещё, раз уж вы ссылку на stackoverflow тут кинули (руки бы оторвал разработчикам этого сайта, но это нынче едва ли не всех сайтов касается) — там вам уже показали пример, когда такие идентификаторы "имеют смысл, потому что задают размерность массива в очередном параметре". Ну так вот, там не уточнили, что, во-первых, это C99; во-вторых, это VLA; в-третьих, в приличных проектах за использование VLA отрывают голову, даже если другие возможности из C99 не запрещены.
Тут сбивало с
Тут сбивало с панталыку именно то, что запрещалось использовать второй идентификатор. Далее уже пошли размышления на тему "почему нельзя, если всё работает" и это натолкнуло на мысль, что вы имели ввиду какие-то эстетические соображения оформления кода, поэтому и я и дал волю чувствам выбирать между двух уродств, несмотря на то, что ни тот ни другой случай в реальном коде писать недопустимо.
Первый том
Первый том очень понятный(за исключением параграфов 1.5.5-1.5.7) и помог мне сделать хоть какой-то прогресс. Сделал игру "змейку", использовав ваш код про очередь и про "звездочку" и немножко их изменив. С другими книгами по программированию я топтался на месте так и не закончив. Спасибо большое вам за книги! Какие книги посоветуете на счет параграфов 1.5.5-1.5.7?
А что, параграф
А что, параграф 1.5.4 (который про бесконечности) осилили? Если да, то всё в порядке, просто забейте на 1.5.5--1.5.7, вернётесь к ним позже, когда освоитесь с программированием и будете примерно представлять, о чём идёт речь. Если же нет -- ну, это хуже, но программировать можно и без этого.
Такой литературы по теории вычислимости и теории алгоритмов, которая была бы ещё проще в изложении, чем это сделано у меня, я никогда не видел. По этим темам вообще с популярными текстами напряжёнка, а рекомендовать вам вузовские учебники и тем паче научные монографии явно бессмысленно.
В введении в
В введении в программирование на странице 30 допущена неточность: Мнемоника jl есть сокращение от "jump if Less", а не Lower.
Вы делаете
Вы делаете весьма распространённую ошибку. Нет, на самом деле именно Lower, как противоположность Greater. Противоположные мнемоники используют в названии букву g (jg, jge, jng, jnge). Если бы jl было от "less", противоположным словом было бы "more", а мнемоники были бы jm, jme, jnm, jnme. Кстати, "jump if no more" мне чем-то нравится по звучанию :-)
UPD: Вынужден взять свои слова назад, был неправ. Посмотрел разнообразные справочники, везде именно less, lower вообще нигде не встречается. Видимо, это некая языковая тонкость, которая от меня ускользнула. Приношу свои извинения за попытку дезинформации.
Обращаю ваше
Обращаю ваше внимание на то, что на странице 65 той же книги вы пишите: "... в процессорое i386 предусмотрена команда jl (от слов jump if less than ...)"
А вот это уже
А вот это уже опечатка, да. Спасибо.
Желательно ввести дополнительный комментарий
День добрый.
В книге встречаются примеры вида
double dbl_sum(const double *a, int size)
{
return size > 0 ? *a + dbl_sum(a+1, size-1): 0;
}
(стр. 394, вторая книга)
понятно, что пример учебный, дабы "вбить в голову" как работает рекурсия лишний раз. И возможно даже компилятор его оптимизирует в итерацию. Но все же не плохо бы уточнить каждый раз по возможности комментарием вида "не смотря на изящность решения, при большом размере массива лучше так не делать, пример учебный, чтоб закрепить понимание рекурсии". А то ведь студенты читают.
Эту рекурсию
Эту рекурсию нельзя преобразовать в итерацию, она же не хвостовая. По поводу комментария подумаю.
Опечатка стр 326 том 2
Опечатка в фрагменте кода внизу страницы.
Написано:
if(first){
first = first->next;
while(first){
free(first->last);
first = first->next;
...
должно быть:
f(first){
first = first->next;
while(first){
free(first->prev); /* here was error */
first = first->next;
...
P.S. все же не удобно пользоваться PDF без поиска и возможности копирования.
Эту уже нашли,
Эту уже нашли, там ещё абзацем выше вместо first должно быть last. Спасибо за внимательность :-)
Ошибка ?
Во втором томе на стр. 182 сказано:
В результате сравнения при неравенстве операндов C3->0, C0->1 (если st0 > st1), или ->0 (если st0 < st1). ... флаг C0 оказывается аналогичен флагу CF (для беззнаковых чисел).
Но ведь это не так. При сравнении беззнаковых чисел, CF устанавливается в 1, если уменьшаемое меньше вычитаемого.
В табл. 3.3 описывается jb (a < b) (CF=1).
Например программа ниже, на моем компьютере, выводит CF=0.
При этом все становится на свои места, если сказать:
В результате сравнения при неравенстве операндов C3->0, C0->0 (если st0 > st1), или ->1 (если st0 < st1).
Там перепутаны
Там перепутаны "больше" и "меньше", это уже обнаружили.
А что у вас за print и syscall? Вроде никаких файлов с макросами не подключается?
print и syscall - мои макросы
В программе действительно не хватает "%include ...". Это ошибка, впрочем, не меняющая сути.
На стр.157
На стр.157 второго тома есть код:
err2len equ $-err1msg
Вероятно должно быть:
err2len equ $-err2msg
Ффффффак... в смысле, факт
Что характерно, в архиве примеров в файле copy.asm эта фигня тоже есть, а в файле asmcopy/message.asm, где имеется копия того же самого фрагмента, огрех таки исправлен. Вот мне, наверное, хорошо было, когда я это всё писал.
Большое спасибо за выявление этой ошибки, это очень своевременно — теперь во втором издании будет на одну ошибку меньше.
Опечатка
Здравствуйте. На странице 122 есть фрагмент кода:
mov ecx, array
mov esi, arr_len
Вы перепутали ecx и esi местами.
Должно быть:
mov ecx, arr_len
mov esi, array
Большое спасибо за великолепные книги.
Да нет, почему
Там же как раз пример "что будет, если". Чтобы всё сломалось, их как раз и надо было перепутать.
Опечатка к первому тому
Добрый день!
Стр 376. "pp:= @first" должно быть pp:= first
Да неужели? :-)
Перечитайте параграф с самого начала, вы в нём, по-видимому, ничего не поняли. NB: если здесь убрать операцию взятия адреса, получится несоответствие типов, поскольку first имеет тип itemptr (указатель на item), тогда как pp имеет тип ^itemptr, т.е. указатель на указатель на item.
Том 2 Стр. 371 Опечатка
"Что касается дополнительного отступа для для всего тела..."
Уже известна,
Уже известна, но всё равно спасибо :-)
Том 1 Стр. 54 Опечатка
"... регистры - запоминающие устройства, способные хранить от нескольких до нескольких десятков двоичных разрядов ...". Вероятно пропустили слово 'единиц' после первого 'нескольких':)
Да нет, почему.
Да нет, почему. Ну то есть я согласен, получилось коряво, но с "единицами", по-моему, будет ещё корявее. Надо подумать, как это переформулировать.
Ошибка в первом томе.
К сожалению я не в курсе, исправлял ли кто-то её или нет.
На 261 странице первого (!) тома сказано:
""... 7Е3 -- это то же самое что 700.0 ..."
Что не верно. Ибо 7Е3 = 7000.0 а не 700.0
Спасибо!
Раньше эту ошибку никто не заметил, и вы тут совершенно правы. Большое спасибо.
На странице 399, Том 2 нашлась очепятка )
Андрей Викторович, здравствуйте!
На странице 399 Тома 2 в описании функции обнаружил опечатку:
...
int_bin_tree_traverse(r->left);
(*callback)(r->val, userdata);
int_bin_tree_traverse(r->right);
...
Не хватает параметров в рекурсивных вызовах. Вот такая штука у меня заработала:
...
int_bin_tree_traverse(r->left, callback, userdata);
....
int_bin_tree_traverse(r->right, callback, userdata);
Спасибо Вам огромное за Ваши труды!!! Успехов во всех начинаниях!!!
Есть такая, уже
Есть такая, уже нашли. Вы совершенно правы, спасибо за внимательность.
Недопонимание
На 154 странице 2-ого тома фрагмент кода:
Не могу понять, почему к esp прибавляется
(%0-1)*4
вместо%0*4
, ведь командаpush dword %1
вызывалась%0
раз.Это просто ошибка
Её даже недавно выловили, хотя перед этим она просуществовала больше десяти лет. Должно быть действительно
(%0 * 4)
.Добрый день.
Добрый день. Второй том, с. 442:
реультат макропроцессирования выдаётся на стандартный вывод (в абзаце про ключ -E)
Касательно -S: можно добавить -masm=intel и получить ассемблерный выхлоп с синтаксисом Intel.
thanks
Спасибо, опечатку вы заметили первым. Про asm=intel тоже стоит упомянуть, во втором издании добавлю.
Опечатки в I томе
Комментарии к I тому закрыты, поэтому пишу сюда.
Прошу проверить возможные опечатки:
с. 243, 258, 284, 300, 328, 332, 366, 391, 395
";" в строках перед end.
c. 253
Вызов процедуры должен быть
PrintChars(ch; count - 1)
с. 278
Вместо ''''' требуется '''
с. 331
Массив AllColors не меняется, можно задать как const.
При вызове процедуры MakeLine происходит лишнее неявное преобразование типа
fgcolor - ожидается integer, подаётся word.
Можно исправить описание процедуры:
procedure MakeLine(line: integer; fgcolor: word)
Лишняя ";" после вызова MakeLine.
*в книге не описана возможность занесения в массив элементов типа word
значений типа string: (Black, Blue, Green, Cyan...) как в перечислимый тип.
c. 371
Если решается 1я задача со стр. 359, надо использовать read вместо readln - без
перевода строки.
Вызов функции SOLEmpty(s) д.б. SOLIsEmpty(s).
c. 373
В функциях QOLGet и QOLIsEmpty queue^.first и queue^.last заменить на
queue.first и queue.last.
c. 383
Параметры функций и процедур должны разделяться ";", а не ",".
c. 398
if MatchIdx(idxs+i, idxp+1) then
заменить на
if MatchIdx(str, pat, idxs+i, idxp+1) then
с. 399
На с. 398 в тексте предполагается, что Match использует параметры-значения, но
функция описана с параметрами-переменными - испарвить на
function Match(str, pat: string): boolean;
if ParamCount < 3 then
заменить на
if ParamCount < 2 then
Ожидается 2 параметра.
c. 437 (2 места)
[ x"$c $d" != x"$res" ] заменить на
[ "$c $d" != "$res" ]
Большое спасибо за книги!
Круто
с. 243, 258, 284, 300, 328, 332, 366, 391, 395
Из всего этого великолепия мне известно было только об одном фрагменте, остальные вы нашли первым. Надо сказать, я впечатлён.
(они, конечно, и так работают — и тем труднее такие штуки вылавливать замыленным взглядом, особенно для человека, привыкшего к Си, а я именно такой)
c. 253
Уже известно, но всё равно спасибо за внимательность :-)
с. 278
Вот ни фига. Там идея в том, что перед символом выдаются два обратных апострофа, а после символа -- два прямых. А чтобы их выдать, нужно на каждый внутри строки нарисовать два подряд, что там и сделано -- первый открывает строковый литерал, следующие четыре дают два символа апострофа. При всём при этом обратные апострофы -- это простые символы, их "удваивать" не нужно.
с. 331 Массив AllColors не меняется, можно задать как const.
Я сознательно отказался от рассмотрения типизированных констант (см. замечание на стр. 260), а нетипизированных таких вроде бы не бывает.
лишнее неявное преобразование типа
Пожалуй, соглашусь, хотя ни лишнего машинного кода, ни потери информации здесь не происходит.
*в книге не описана возможность занесения в массив элементов типа word значений типа string: (Black, Blue, Green, Cyan...) как в перечислимый тип.
Я не вполне понимаю, при чём тут перечислимый тип и тем более тип String. Инициализация массивов описана на стр.300-301.
с. 371
тоже уже известный косяк
SOLIsEmpty(s)
Да, факт. Спасибо.
с. 373
И про это тоже уже знаю
с. 383
Да, факт, и это вы первый заметили.
c. 398
Тоже вы первый заметили, спасибо. В исходнике в архиве примеров этот косяк поправлен (что и понятно, пример пробовался в деле), а в текст рукописи я это, видимо, перенести забыл.
с. 399
и с этим та же ситуация
c. 437 (2 места)
Я привык к тому, что аргументы команды test (она же квадратные скобки) не могут быть пустыми. В современных версиях bash и sh этого ограничения уже нет, как я только что выяснил. Видимо, к следующму изданию всё-таки поправлю.
Возможно опечатка
На странице 284 6-я строчка сверху: "... выбираемое в зависимости от значения perror". Думаю должно быть не perror, а errno.
Да, факт.
Спасибо.
функция string_length
Здравствуйте, на странице 262 последний вариант функции string_length возвращает на 1 больше(считает нулевой символ).
Good catch
Спасибо! Видимо, раньше никто не пытался всерьёз анализировать эту функцию на предмет возможных ошибок, как это предложено в тексте :-) Пожалуй, я её саму исправлять не буду, скорректирую текст после неё, предложив читателю отыскать ошибку, которая тут точно есть.
Добрый день. В
Добрый день. В первом томе на стр. 143: "и совершенно неважо, какова будет итоговая формулировка".
Спасибо, эту вы
Спасибо, эту вы заметили первым.
Добрый день, во
Добрый день, во втором томе на стр 216 ошибка в функции case_up, вместо return c-('a'-'Z'); по-видимому, должно быть
return c-(('a'-'Z')+('Z'-'A'));
Спасибо.
Есть такое, но
Есть такое, но уже известно.
Добрый день,
Добрый день, возможно уже исправлено: в первом томе на стр 340 oбъявляется переменная 'mult', далее используется 'mul'. Большое спасибо!
Эту тоже уже
Эту тоже уже нашли, но спасибо за внимательность :)
Возможно уже
Возможно уже исправляли:
стр. 292, вторая строка сверху: "знакомую нам тех пор", видимо должно быть: "знакомую нам с тех пор".
Большое спасибо за труд.
Да, эта у меня
Да, эта у меня уже помечена, но всё равно спасибо :-)
Битовые поля
Добрый день, Андрей Викторович. Позвольте небольшой комментарий (кажется, здесь об этом еще никто не писал) касательно битовых полей. До появления "стандартов" спецификация Си позволяла указывать ширину только для [un]signed int (в C99 к ним примазался еще bool, который на самом деле int). Указание ширины для других типов - нестандартная возможность GCC (sic!), ставшая со временем чем-то самим собой разумеющимся. Но, например, структура с unsigned char на с. 336 не понравится компилятору, запущенному с -ansi -pedantic; у Кернигана и Ритчи этот момент также оговаривается. Возможно, если дойдет дело до переиздания, стоит об этом упомянуть.
Да, косяк с моей
Да, косяк с моей стороны. Обязательно поправлю при переиздании.
Опечатки
Прошу проверить возможные опечатки:
с. 73
"статус объекта с номером 510 - в 29-м бите 15-го элемента массива".
Должен быть 30й бит 15-го элемента массива. 510/32 = 15+30/32.
с. 75
При очистке массива
lp: mov [esi+4*ecx], eax
массив очистится с адреса [esi+4] до [esi + 4*15], а требуется с [esi] до [esi + 4*15].
Т.е. программа должна выглядеть:
xor eax, eax
> mov ecx, 16
mov esi, set512
>lp: mov [esi+4*ecx-4], eax
loop lp
Как в примере на с. 69.
В последнем примере на с. 76 - то же самое.
с. 108
"...четыре (байта) расходуются на представление числа 1000"
Д.б. 10000.
с. 154
В строке
add esp, (%0-1)*4
[esp] будет указывать на последний параметр макроса, он так и останется на вершине стэка. Предполагалось убрать все параметры.
Должно быть:
add esp, %0*4
с. 157
В программе copy.asm не обрабатываются ошибки по результату выполнения макросов kernel для вызовов read и write.
На с. 145 вы предупреждаете:
"При чтении, как и при использовании других системных вызовов, может произойти ошибка ... это обнаруживается по "отрицательному" значению регистра eax после возврата из вызова".
с. 159
.again:
kernel 3, [fdsrc], buffer, bufsize
cmp eax, 0
jle .end_of_file
...
Производится проверка eax <= 0 и для ситуации конца файла (eax = 0) и для ошибки (eax < 0). При выполнении условия следует переход для обработки ситуации конца файла, но нет обработки ситуации ошибки (когда eax < 0), которую надо обрабатывать отдельно - аналогично вызовам open на с. 158.
Если в программе не предполагается обработка ошибок для read и write, можно было ограничиться проверкой eax = 0:
cmp eax, 0
je .end_of_file
с. 183
В коде строка fxcn д.б. fxch.
с. 210
В вызове функции
d = discr(p, q, r)
ф-я должна называться discrim.
с. 241
for(i = 0, j = arr_len - 1; i < j; i++, j++)
должно быть
for(i = 0, j = arr_len - 1; i < j; i++, j--)
иначе - бесконечный цикл.
с. 326
В примере
if(last)
...
last = first->prev
должно быть
last = last->prev
В примере
if(first)
...
free(first->last)
должно быть
free(first->prev)
с. 336
"...на который отведено 4 бита, т.е. он может принимать значения от 0 до 16".
Должно быть "от 0 до 15".
с. 428
Предложение:
т.к. часть
pn = setpair(fgcolor, all_colors[i]);
att = COLOR_PAIR(pn);
не меняется во вложенном цикле, можно вынести её во внешний.
Большое спасибо за книги!
Спасибо!
> с. 73
спасибо, эту вы заметили первым
> с. 75
про эту уже знаю :-)
> с. 108
эту тоже вы первым заметили
> с. 154
И в самом деле, поместили в стек %0 dword'ов, а вычищаем %0-1. То есть вы совершенно правы, хотя я это не сразу понял. Здесь не было учтено "волшебное" двойное слово, характерное для системных вызовов FreeBSD. И тоже этого до вас никто не заметил, хотя этому примеру уже около десяти лет — он присутствовал ещё в самом первом издании "книжки по ассемблеру, вышедшем в 2010 году.
> с. 157
Единственный вызов kernel на этой странице -- вывод сообщения о неправильном количестве аргументов в поток диагностики. Ошибки на stdout'е и stderr'е практически никогда не проверяются -- я не припомню случая, чтобы хоть раз такое видел.
> с. 159
См. текст строчкой выше (непосредственно перед примером кода). И никто тут никому ничего не должен, чтение до конца файла обычно именно так и выполняют — а с учётом того, что здесь у нас язык ассемблера, загромождать примеры раздельной обработкой ошибки и конца файла вообще ни к чему. А вот "ограничиваться проверкой на ноль", как вы предлагаете — нельзя категорически: если ошибка таки случится, программа зациклится (зависнет).
> с. 183
> с. 210
> с. 241
> с. 326
> с. 336
Уже известны, но всё равно спасибо за внимательность :)
> с. 428
att дальше в теле цикла изменяется, так что нет, её нельзя выносить во внешний цикл, приходится вычислять каждый раз. Первую из двух строчек (где pn вычисляется) — можно, но при этом несколько пострадает наглядность — два связанных между собой вычисления окажутся разнесены по коду.
Большое спасибо за выявление ошибок — всё, что вы обнаружили нового, я в своём экземпляре пометил, так что ко второму изданию это всё будет исправлено.
Опечатка?
В третьем томе на странице 181 подсеть в локальной сети Бориса, похоже, обозначена с ошибкой. Видится, вместо адреса подсети 192.168.64.0 должен быть адрес 192.168.45.64.
Если всё же это не опечатка, не могли бы Вы поподробнее объяснить, откуда этот адрес взялся
Абсолютно
Абсолютно верно, на рисунке ошибка. Спасибо, до вас на неё никто внимания не обратил.
Возможная опечатка
Полагаю, в третьем томе на странице 113 в предложении "Вызов sigaction появился позже, имеет..." пропущено слово "который"
Да нет, тут всё
Да нет, тут всё так и задумано, структура предложения корректна.
Там, кстати, строчкой выше есть опечатка — не согласован род (написано "была" вместо "было"), но про неё я уже знаю :-)
Опечатка на стр. 120
Здравствуйте. В сноске 37 на странице 120 опечатка в числах
из ряда Фибоначчи.
Да, факт
Спасибо :)
не могу запустить 32 битный эльф на 64 бит
Здравствуйте
Пробую собрать первую программу на Nasm
$ nasm -f elf32 1.asm
$ ld -m elf_i386 1.o -o 1
$ ./1
-bash: ./1: cannot execute binary file: Exec format error
$ ldd 1
not a dynamic executable
В хекс редакторе видно, что это эльф, 32 битный, т.е. все ок, но почему не запускается?
Ставил
sudo apt-get install libc6-dev-i386
и еще много чего, что находил в инете - толку 0. Убунта 64 бита.
странно
libc ставить уж точно бессмысленно, это стандартная библиотека Си, программы на ассемблере в ней не нуждаются. Собственно, примеры, разобранные в книжке, вообще не нуждаются ни в каких библиотеках. И ldd тут, разумеется, ни при чём, динамическую линковку я в книжке не рассматривал.
Причин, по которым может не работать — увы, не знаю. Теоретически может флаг noexec висеть на рабочем разделе, но вряд ли, вроде бы убунта в таком не замечена.
А какой дистр убунты и что говорит команда "uname -a"? И такой вопрос: на других языках (на Паскале или Си) программы (пёс с ними, что 64-битные) из той же директории запускаются? Просить вас сделать 32-битную программу на Си не буду, там слишком много чего надо учесть.
извините
Вы будете ругаться, но - я это тестил в WSL убунте. Оказалось, что там нет поддержки 32 бит, хоть как не старайся. Поставил нормальную и все заработало. Еще раз извините.
p.s. капча очень сложная, с плохим зрением по десять раз ввожу - не понимаю, что.
Пример на ncurses (movingstar)
В примере movingstar, реализованном на ncurses, есть особенность, которую можно при определённых условиях считать багом: если зажать клавишу со стрелкой и не отпускать, то звёздочка остановится. Конечно, говорить о баге (или фиче :)) в учебном примере несколько странно, так как там нет конкретных требований, однако думаю, что для большинства практических примеров (например, консольных игрушек) такое поведение программы нежелательно. Кроме того, в примере movingstar на паскале из первого тома этого бага нет.
Причина такого поведения вполне понятна. Вот кусок кода этого примера.
Ясно, что если нажать, например, на стрелку "вверх" и не отпускать, то getch будет всё время возвращать KEY_UP и никогда не вернёт ERR, то есть move_star не будет вызван. Можно было бы вынести move_star из тела switch и делать его на каждой итерации цикла, но тогда возникнет другая проблема: если зажать клавишу, то getch начнёт отдавать управление слишком быстро (не дожидаясь окончания timeout-а), и звёздочка начнёт ускоряться при зажатии стрелки.
По-видимому, правильным решением здесь будет отказаться от использования timeout и организовывать задержку с помощью функции sleep или подобной. При этом считывание клавиш имеет смысл производить чаще, чем собственно двигать точку.
Можно и так, да.
Можно и так, да. А в реальной ситуации, скорее всего, будет как-то ещё. Задача примера — иллюстративная, тут заодно ещё и тайм-аут показан в действии. Хотя, пожалуй, следует обратить внимание читателя на найденный вами недочёт и предложить самостоятельно подумать, как это исправить.
Я бы только один момент здесь отметил: в чистом Си использование строчных комментариев -- моветон, несмотря на то, что все (реально все, особенно после "легализации" этого в стандартах) это допускают.
Можно и так, да.
Можно и так, да. А в реальной ситуации, скорее всего, будет как-то ещё.
Ну, в принципе, пример, который я привёл, с небольшими изменениями взят из моего личного, пусть и не очень масштабного, проекта (это консольная игра-змейка), так что ситуация в каком-то смысле вполне реальная. Впрочем, программа пока далека от завершения, поэтому не исключено, что к тому моменту этот фрагмент кода ещё поменяется.
Я бы только один момент здесь отметил: в чистом Си использование строчных комментариев -- моветон, несмотря на то, что все (реально все, особенно после "легализации" этого в стандартах) это допускают.
Так вышло, что на чистом Си мне писать почти не доводилось, а в Си++ строчные комментарии в порядке вещей. Строчные комментарии не допускаются в чистом Си, в случае если включить флаги компилятора --std=c89 --pedantic. Во многих проектах такие флаги включены, и обычно там явно прописано, что использование строчных комментариев не допускается. Но честно говоря, в реальности я не вижу ни одной проблемы, которая могла бы возникнуть от их использования, за исключением, возможно, каких-либо проблем с переносимостью в случае использования экзотического компилятора. В конце концов, строчные комментарии пришли не из стандарта, а из Си++, и они ничем не хуже ключевых слов const и inline. Но в любом случае в каждом проекте требования к коду свои.
Ну, и в любом случае, в примере кода на сайте (в отличие от реальной программы) даже русские буквы могут быть в комментарии, не говоря уже о формате комментария :)
так что
так что ситуация в каком-то смысле вполне реальная.
Я за вас рад, и что? Ещё раз, и медленно: основной критерий выбора того или иного способа решения в книжке — иллюстративная ценность примера. Практическое программирование меня волнует в самую последнюю очередь, этому можно научиться без книжек.
в Си++ строчные комментарии в порядке вещей
Совершенно верно. Как верно и то, что Си++ — принципиально другой язык с принципиально другими традициями и для принципиально других целей.
в реальности я не вижу ни одной проблемы
Вы читать умеете? Я не сказал, что это может создать проблемы, я сказал, что это моветон. Если угодно, демонстрация отсутствия вкуса. Или, если прагматичнее — то, что может навести на мысль об отсутствии у автора программы чёткого понимания, что Си и Си++ — это совершенно разные языки.
Занятые порты
Если процесс взаимодействует с чем-то по сети, то он должен избрать порт для своего сокета. Понятно, что порт нужно выбирать из тех, которые на данный момент свободны. Возникает вопрос - как внутри программы узнать, какие порты в системе уже заняты на данный момент?
В терминале это узнать можно, если я не ошибаюсь, через что-то вроде:
netstat -at | tail -n +3 | grep -v 'LISTEN' | sed 's/\ \{1,\}/\ /g' |
cut -d' ' -f 4 | sed 's/cpu_name://' | sort -u
(На правильность не претендую, но такой вариант у меня сработал)
В принципе, можно изнутри программы запустить данный конвейер с использованием неименованных каналов, а потом прочитать выдачу. Но выглядит это, мягко говоря, довольно криво и неестественно.
Есть ли более правильный способ решить упомянутую проблему?
Ой 8-()
Сколько лет использую сокеты, ни разу этой проблемы не возникло :-) Даже была одно время такая проблема, как определить, какие есть ip-адреса на данном хосте (и, кстати, похоже, что никак, во всяком случае переносимого способа я не нашёл), но вот чтобы порты... :)
Дело в чём, когда вы делаете сервер, вам нужно не "свободный" порт, а какой-то конкретный, и если он занят — можно со спокойной душой падать. Причина тут чисто организационная: клиенты ожидают вас найти на каком-то заданном порту, и именно этот порт сисадмин должен указать в настройках сервера, и всё, никакой свободы.
Если же вы делаете клиента, то вам в подавляющем большинстве случаев сугубо пофигу, какой будет порт. Просто не делаете
bind
и всё, вызываетеsocket
и сразу за нимconnect
. Система сама выдаст вашему сокету свободный порт, есть даже какое-то соглашение о том, из какого промежутка это будет порт. Ну а в тех крайне малочисленных случаях, когда нужен именно определённый порт, этот порт вашему серверу тоже сообщат через конфигурационный файл, параметры командной строки и т.д.Если же всё-таки такая задача встала, то уж, конечно, внешние программы запускать не надо. Самое простое, что приходит в голову — запустить цикл
bind
'ов с возрастанием номера порта по принципу «пока свободный не найдём».Если интересно, откуда netstat свою информацию черпает — добро пожаловать в файловую систему
/proc
:-) Только не надо это руками делать, совершенно не переносимое решение.Не подумал
Я почему-то даже не подумал, что можно не использовать bind, если не важно, какой порт. Тогда, действительно, всё значительно упрощается.
Что касается цикла bind'ов: это было попробовано, но почему-то получалось, что при работающем сервере и при одном запущенном на данном компьютере клиентском процессе второй успешно "биндится" с тем же портом, но после этого не проходит connect. Так что в цикл я поместил и bind, и connect, после чего оказалось, что bind к сокету с адресом применять нельзя (EINVAL) (чего и следовало ожидать). В общем, без "костылей" я обойти этим способом проблему не смог.
Я почему-то
Я почему-то даже не подумал, что можно не использовать bind
Том 3, стр. 211, второй абзац снизу :-) (сразу перед прототипом вызова
connect
)почему-то получалось, что при работающем сервере и при одном запущенном на данном компьютере клиентском процессе второй успешно "биндится" с тем же портом
Рискну предположить, что вы на этот сокет повесили SO_REUSEADDR, как в том же третьем томе на стр. 215 предлагается. Так вот это там предлагается для слушающих портов делать, чтоб не залипали, а отнюдь не для всех подряд.
Спасибо!Что-то
Спасибо!
Что-то совсем невнимательно читал эту главу.
Самостоятельного разгадывания ребуса не случилось.
(стр. 326) Не могли бы Вы уточнить зачем взятие адреса, а потом dereference за скобкой. Нельзя ли так:
first ? first->prev : last = tmp;
Нет, нельзя.
Условная операция работает со значениями, а не с "переменными как таковыми". Как следствие, результатом тернарной операции будет адрес (то есть значение адресного типа), а не указатель, которому можно что-то присвоить.
Стало
Стало интересно, проверил. В чистом Си действительно нельзя, а в Си++ можно. Там результат тернарного оператора считается lvalue, возможно, из-за появления в языке ссылок:
Компилятор gcc этот код действительно не скомпилирует с ошибкой:
А если переименовать файл в .cpp и вызвать g++ вместо gcc, то происходит чудо:
Интересно
Я этого не знал, спасибо.
В принципе это логично, и да, логично именно из-за появления ссылок — если считать имя переменной выражением типа ссылка. Вопрос лишь в том, какая из спецификаций C++ это позволяет (вполне возможно, что это GNU extension).
Насколько я
Насколько я понимаю, это именно по стандарту так, причём начиная с C++98. И все реально существующие компиляторы (gcc, clang, msvc) это поддерживают.
Естественно, выражение (cond ? a : b) будет lvalue только в случае, когда и a, и b -- lvalue одного и того же типа, что, в принципе, логично. Возможно, есть ещё какие-то случаи, там логика довольно мутная, особенно в C++11/14/17, причём даже между ними есть различия. Подробно это описано, например, здесь.
Односвязный список и рекурсия
На странице 322 утверждается, что удаление односвязного списка делается "изящнее" с помощью рекурсии. Насколько я понимаю, при всём своём изяществе такое решение не работает в реальной жизни, так как глубина рекурсии в этом случае равна длине списка, то есть может быть весьма большой. В этом случае добавить элемент в список мы сможем (так как памяти хватит), а вот при удалении всё это в лучшем случае благополучно упадёт с переполнением стека.
Что такое
Что такое "весьма большой"? В подавляющем большинстве случаев, во всяком случае, из моей личной практики, списки не превышают сотню элементов; крайне редко встречаются задачи, где списки имеют длину порядка тысяч или десятков тысяч элементов. Глубина в 10000 рекурсивных вызовов — это пока ещё далеко от опасного предела (размер фрейма при рекурсивном удалении списка будет 16 байт на 32-битной архитектуре, 32 байта на 64-битной, а под стек в большинстве случаев выделяется 8Mb, так что там ещё больше порядка в запасе).
Задачи, в которой фигурировал бы список на 100.000+ элементов, я не встречал ни разу за всю жизнь.
Это всё, впрочем, не значит, что надо непременно удалять списки рекурсивными процедурами. Открою секрет — я сам их почти всегда циклом удаляю. Но причина тут уж точно не в переполнении стека.
Не убедительно
Так ведь дело не в том, что список может быть большим в реальной жизни, а в том, что он зависит от пользовательских данных. И переполнение стека, пусть даже и в нереалистичной ситуации, создаёт уязвимость в программе, как и в случае с использованием gets. Тут Вы, конечно, можете возразить, что в программу можно поставить внешнюю проверку, чтобы она не позволяла пользователю добавлять больше определённого количества элементов. Но тогда возникнет неприятный вопрос о том, какое же именно число выбрать в качестве этого лимита.
Кроме того, если даже переполнения стека не произойдёт, при отладке программа всё равно может упасть из-за порчи памяти раньше, и стек вызовов глубиной 10000, мягко говоря, не сделает отладку приятнее :)
Чтобы подобных проблем не возникало, разумнее не делать здесь ограничений, а рекурсивное удаление списка считать недопустимым, как и gets.
Справедливости ради, следует отметить, что решение с рекурсией было бы изящным если бы в используемом языке программирования присутствовала оптимизация хвостовой рекурсии. Но поскольку в Си такого не предусмотрено, здесь это приходится делать руками, то есть писать цикл.
Похожая проблема возникает при реализации алгоритма быстрой сортировки Хоара (quicksort): когда мы сортируем две части массива, то в худшем случае глубина рекурсии может достигнуть длины массива со всеми вытекающими последствиями, поэтому там тоже приходится сначала рекурсивно сортировать массив меньшей длины, а второй рекурсивный вызов заменять на внешний цикл.
При
При использовании gets переполняется не стек, а та переменная, куда читают. Если она при этом в стеке, то можно затереть адрес возврата и всё такое прочее.
Переполнение стека из-за слишком глубокой рекурсии не создаёт никакой уязвимости кроме разве что denial of service, ибо программа при этом тупо падает. Заметим, она и по переполнению кучи может упасть, различие чисто количественное.
Остальное комментировать не вижу никакого смысла.
Cложные описания и модификатор const
Как применить правила, описанные в разделе 4.13.3, к описаниям со страницы 259?
const int *p;
p -- это указатель на int константный;
int * const q;
q -- это константный указатель на int.
Не могу понять, почему зависимое слово "константный" в словесном описании в первом случае стоит после главного слова, а в другом -- перед ним.
И ещё вопрос из той же оперы: как описать "указатель на константную область памяти, в которой находятся указатели на константные области памяти, в которых хранятся int'ы"?
const int *p; p -- это
const int *p;
p -- это указатель на int константный;
здесь "константный" относится не к указателю, а к int. То есть это указатель на целочисленную константу.
Понятно, что все подобные правила условны: создатели языка Си по-русски не говорили и не ставили себе целью соответствовать его традициям, да и не достигли бы этих целей всё равно.
"указатель на константную область памяти, в которой находятся указатели на константные области памяти, в которых хранятся int'ы"?
const int * const *p;
здесь
здесь "константный" относится не к указателю, а к int. То есть это указатель на целочисленную константу.
Да, это я понимаю. Получается, что "константный инт" соответствует "const int", а константному указателю - "* const".
Если, например, взять ваш ответ на мой второй вопрос:
const int * const *p;
>> ...*p
p - это указатель на...
>> ...*const...
неизменяемый указатель на
>> const int...
неизменяемый int.
То есть в случае с указателями const стоит справа от "указателя", а в случае со стандартными типами - слева.
Вот это несоответствие и смущало.
То есть в
То есть в случае с указателями const стоит справа от "указателя", а в случае со стандартными типами - слева.
Вот это несоответствие и смущало.
Видимо, всё-таки вы не всё поняли. Например:
const int *p;
int const *p;
это строго одно и то же, то есть эти два описания вообще ничем не различаются. Принципиально не то, как расположены const и int, а то, как расположены const и звёздочка.
Печать регистра
Андрей Викторович, я занимаюсь по Вашей книге и не могу пока понять как вывести на экран число, которое содержится в регистре. Возможно, информация, которая поможет это сделать, будет написана дальше, но мне уже в самом начале необходимо для тестирования своих программ эта возможность. Поискал в интернете разные примеры, но не уверен, что хорошая идея запускать что-то из сети, не зная что. Заранее благодарю за помощь и за книги, которые Вы написали!
Гыгыгыгыгы
Знаете, как в том анекдоте было: приходит ветеринар к терапевту, ну терапевт его спрашивает, мол, на что жалуетесь? Ветеринар разочарованным голосом: "не, ну так-то каждый дурак может".
Понимаете, ассемблер — это не Паскаль, не Си, не питончик какой-нибудь. Там есть только те возможности, которыми обладает процессор. Процессор не умеет сам по себе ничего печатать, и, более того, процессор не может даже перевести число в десятичную систему.
Сделайте себе, например, фрагмент кода, который будет печатать столько звёздочек, какое значение сейчас в EAX. Делается, как мы понимаем, элементарно. На первых порах вас это вполне спасёт.
Что до печати числа — это упражнение, не шибко сложное, но и не совсем тривиальное; я, когда кого-то учу ассемблеру, эту задачу всегда рассматриваю как обязательную. И вам настоятельно советую решить её самостоятельно, она того стоит. Лучше всего разбить её на подзадачи: написать отдельно подпрограмму, которая размещает десятичное представление заданного числа в указанной области памяти, отдельно подпрограмму, которая подсчитывает длину строки (в предположении, что в конце строки располагается "нулевой символ") и дальше тупо обратиться к системному вызову write.
Понял. Спасибо!
Понял. Спасибо! А не планируется книга с заданиями? Или, может быть, можете посоветовать, где посмотреть их. Очень бы пригодилось...
Планируется-планируется
Задачник — это следующий этап проекта после четвёртого тома. К сожалению, назвать конкретные сроки его выхода тяжело.
Кодировка PDF-файла
Здравствуйте. Подскажите, пожалуйста, в какой кодировке находится текст данной книги? У меня ubuntu-based дистрибутив, текст копируется битыми символами. Я помню, что в одной из Ваших книг по NASM я использовал онлайн-перекодировщик, но все настройки потерялись и мне не хотелось бы снова тратить время на их поиск или на распознавание её типа. Копирование для электронного конспекта. Спасибо.
Ни в какой
Текстовый слой в PDF-файлах, представленных на этом сайте, преднамеренно сломан. Рекомендую воспринимать эти файлы как аналог бумажной книги, только без бумаги. В бумажных книгах тоже нет ни поиска, ни возможности копирования в конспект.
Взаимодействия процессов
Здравствуйте Андрей Викторович, столкнулся с такой задачей, имеется программа, которая создает процесс и имеет определенные счетчики. Я хочу создать свою программу которая будет пользоваться этими данными для своих целей. Первое что приходит на ум разделяемые данные в оперативке, но вы пишите что этого нужно избегать. Подскажите как лучше организовать взаимодействие между двумя процессами в этой ситуации ?
Первое, что
Первое, что приходит в голову: открыть некий FIFO на запись (причём при открытии использовать O_NONBLOCK, чтоб не "повиснуть"), потом по сигналу (например, SIGUSR1) выплёвывать в этот FIFO значения счётчиков. Тот, кому эти счётчики нужны, открывает тот же FIFO на чтение, когда нужны счётчики - шлёт первому процессу SIGUSR1 и идёт читать, что пришло.
Естественно, возможен целый ряд других решений. И да, я продолжаю настаивать, что решение с разделяемой памятью — самое неудачное из всех возможных.
Запуск на Mac OS (OS X)
Долго пытался запустить первый пример hello5.asm на macOS 10.14.3, пришел к такому решению.
файл stud_io.inc можно не менять
в тексте программы заменить
_start
наstart
получится такой тест программы
запускаем командами
Можно и так
Формат исполняемых файлов там не такой, как под настоящей BSD, так что всё логично. Но вообще у линкера обычно есть параметр, задающий имя метки для точки входа. У GNU ld это флажок -e; если у вашего линкера тоже есть такой параметр, то можно сказать что-то вроде -e _start и не менять текст программы.
Запуск на Mac OS (OS X)
Все-таки файл stud_io.inc нужно чуть поменять, как для FreeBSD, чтобы запускалась программа и работала, иначе только компилируется и собирается.
Я это сделал, когда писал первое сообщение, но забыл указать!
Очевидно
Это понятно, она же не Linux совершенно :-) В примерах, в которых используются в явном виде системные вызовы, следует применять конвенцию FreeBSD.
Linux Namespaces
Андрей Викторович добрый вечер, хотел услышать вашего профессионального мнения по поводу namespaces в Linux, при сборке одного приложения в скрипте использовалась функция unshare. Но в моем дистрибутиве debian по умолчанию отключен в ядре kernel.unprivileged_userns_clone. Я тестировал на виртуальном образе, эта функция через обычного пользователя, запускает процесс с uid root, не совсем понятно пока какому принципу это происходит, без какой либо авторизации. Хотелось бы понять для чего это реализовано и стоит ли использовать в работе? Не просто так она же выключена.
Вообще это (в
Вообще это (в смысле namespaces) сделано для контейнеров типа OpenVZ, эта штука также известна под названием VPS (точнее, это один из способов создания VPS).
«Использовать в работе» для чего-то отличного от контейнеров я бы эту штуку не стал. Иначе говоря, если в ваши планы не входит создание нового OpenVZ, то про namespaces следует забыть нафиг. Про то, как эта unshare работает, ничего сказать не могу, не изучал вопрос.
P.S. и вообще, https://en.wikipedia.org/wiki/Linux_namespaces :-)
1. Добрый вечер
1. Добрый вечер Андрей Викторович, не могли бы пояснить, как правильно реализовать Makefile для файлов паскаль, имею такую структуру директории:
Я написал такой файл:
Как сделать так, чтобы при добавлении новых имен в переменной NAMES = prog_1 prog_2, получался цикл в заголовке цели для каждого имени, а не список всех имен, но при этом не создавать для каждого нового имени - цель? Или так нельзя реализовать?
Еще проведите ликбез, в MacOS и Windows есть калькулятор программиста, там есть логические операции и представление числа в разных системах счисления, как это реализовать в терминале, математические операции я выполняю, передавая строку в калькулятор
А вот логические операции он не понимает, и как можно выводить в поток вывода десятичное число сразу в 16-ой и 2-ой системе? Если просто командой нельзя реализовать так, и для этого нужно писать скрипт то, что использовать для представления?
а оно надо?
Первый вопрос, который приходит в голову: эти ваши файлы .pas представляют собой отдельные программы или составные части одной программы? Если это модули одной программы, make вам не нужен, fpc имеет встроенные возможности для этого: запускаете его для компиляции основной программы, он подцепляет файлы модулей. Если очень хочется модули в отдельную директорию (хотя мне не вполне понятно, зачем) — используйте флаг -Fu.
Далее, чтобы "получался цикл" — делать не надо. Надо в зависимостях цели "all" указать не исходные файлы, а имена получающихся модулей, и без команд; для сборки модулей использовать обобщённую цель (шаблон). См. т.2, стр. 453.
Калькуляторов с логикой не встречал. Что касается вывода чисел в 16-ричной системе, попробуйте
printf '%x'
:Благодарю, всё
Благодарю, всё заработало! Это отдельные программы, про модули теперь буду знать.
От калькулятора отказался, сделал псевдонимы для терминала, теперь все операции выполняет.
Объясните пожалуйста два момента, которые мне не понятны:
1. Если пишу тестовый псевдоним
- он работает и принимает значение аргумента.
- не работает, пришлось делать через функцию, почему не передает значение в выражение?
2. По какому принципу это работает?
По алиасам
По алиасам — если не ошибаюсь, слово function можно опустить, ничего не изменится.
По первому вопросу: это только кажется, что myecho что-то там принимает. На самом деле происходит вот что: слово myecho преобразуется в "echo $1", где $1 заменяется пустой строкой, как и любая неопределённая переменная. Параметр никуда не передаётся, он остаётся где был, но в данном случае это именно то, чего хотели. Попробуйте
— поймёте, что я имел в виду.
По второму вопросу — понятия не имею :-) Я вообще не знаю такого синтаксиса, но я и не претендую на то, что знаю весь Bash.
А я думал, что
А я думал, что псевдоним это как define в Си, очень доходчиво объяснили, да не ошибаетесь, функцию опустил, удобно меньше писанины. Со вторым вопросом разобрался, оказалось всё очень просто, почитал man bash, этот прием называется "Brace Expansion" может кому пригодиться.
Это выражение формирует двойчный массив из 256 элементов, и подставляемое десятичное число является индексом массива, в ячейки которого находится его двойчное представление.
Раздельная трансляция
Добрый день Андрей Викторович, возникло не допонимание видимости объектов, имеется программа состоящая из файлов {main.c; module1.c; module1.h; module2.c; module2.h} в файле module1.h прописано перечисление
enum bool {off,on};
Где еще что нужно правильно прописать, чтобы он виделся в module2.c ? Т.к. когда я про него забыл и прописал еще раз в module2.h компилятор ругнулся, что два одинаковых объявления.
Еще заметил для переменных, если делаю объявление в файле module1.c обязательно нужно как в книге прописать это же объявление в заголовочном файле с extern, а если объявление делаю только в заголовочном файле, без extern, main.c видит эти переменные.
Хорошо усвоил, что не глобальное то static...)))
Гм...
Во-первых и в-главных: так делать не надо. Не нужен такой enum, он только запутает дело и будет мешаться. Логическая ложь -- ноль, всё остальное -- истина, точка, всё, все свободны. Во всяком случае, если мы пишем на чистом Си.
Во-вторых: описание типа -- любого, хоть enum, хоть struct, хоть union, хоть произвольный typedef -- совершенно верно, выносится в заголовочник. А чтобы то, что есть в заголовочнике, было где-то "там" видно, "оттуда" этот заголовочник нужно подключить с помощью #include. А чтобы компилятор не ругался -- прочитайте ещё раз параграф 4.11.4 и сделайте во всех своих заголовочниках защиту от повторного включения (подчёркиваю: НЕ НАДО ДУМАТЬ, НУЖНА ОНА ИЛИ НЕТ! она делается -- по определению -- во всех заголовочных файлах на автопилоте).
Что касается "объявления в заголовочном файле без extern", то руки оторвать тому, кто делал этот линкер. Действительно, это одна из тех ошибок, которые "современный" линкер зачем-то прощает. Очень хочется того, кто это придумал, убивать медленно и мучительно, ибо в этом поведении нет никакой логики, просто вот есть ошибка (грубая! выдающая непонимание происходящего!), которую решили простить.
Уточнение по поводу сказанного
По первому пункту, часто мелкает слово "deprecate" по поводу магических чисел. Значит в чистом Си "0" и "1" к этому не относиться, это нормально использовать их в коде ?
По второму пункту спасибо всё заработало, увидив один проект на просторах, который внёс сомнения. Проект был реализован на плюсах, он состоял из 5 файлов сpp, и 21-ого файла header, в заголовочниках по мимо того, что вы перечислили было class, template, inline. В файлах .cpp прописан только #include "Application.h". Сам Application.h состоит только из включения используемых стандартных библиотек и всех заголовочных файлов проекта. При чем в каждом заголовочнике прописано вместо защиты от повторного включения в начале файла #pragma once. Можете прояснить отличается принцип раздельной компиляции для Си от плюсов, можно это использовать или опять кто-то напридумывал?
Страньше и страньше
В чистом Си — в использовании 0 для обозначения лжи, 1 для обозначения истины не может быть ничего ненормального.
В Си++ есть встроенный тип bool и константы true и false, то есть там подобные enum'ы городить будет вообще ошибкой.
Про всевозможные #pragma забудьте совсем, как страшный сон.
Принципы раздельной компиляции для Си и Си++ не различаются вообще.
А вот если всё, что вы тут рассказываете, относится к Visual Studio (очень похоже на то), то могу дать ещё один совет: снесите её (студию то есть) к чёртовой матери и больше никогда не пытайтесь её использовать.
Нет вы ошибаетесь...
Как начал читать ваш первый том, по вашему же совету снес Windows и поставил Linux и выбрал для себя рабочую среду разработки tmux+vim, очень удобно. По поводу Visual Studio, да вы правы есть похожи сходства c их "Precompiled Headers" и загаловочным файлом "stdafx.h".
Благодарю за пояснения, значит, такие методы не есть хорошо, свой enum убрал, больше не использую, а по поводу bool в Си++, посмотрел в Си тоже сейчас появился в стандарте 99 stdbool.h, с теми же true и false. Можно ли использовать его?
Из C99 не надо
Из C99 не надо использовать ничего. От слова "совсем".
Кто хоть такое
Кто хоть такое по напридумывал, что потом пользоваться нельзя...) Прочитав вашу с соавторами статью «Чистая компиляция как парадигма программирования», сразу хочется предложить воплотить в реальность выводы статьи " ... Если учесть как негативный, так и положительный опыт языка C++, можно создать новый язык ... ".
Кто-кто, известно кто
Напридумывала всё это группа особо опасных международных террористов, по недоразумению называемая комитетом по стандартизации.
А язык — ну, описание его сделать можно, и даже можно слепить компилятор (без серьёзной оптимизации кода — чтобы осилить нынешние методы оптимизации, нужно только ими и заниматься). Да только пользоваться этим никто не будет, увы.
Опечатки
На странице 80 пример в верху страницы. Вроде бы scas сравнивает с содержимым по адресу EDI, а там заносится в ESI. Прошу прощения если повтор.
На странице 79 строка "This is a string" содержит 16 символов (нулевой не рассматривается как я понял). Последний 'g' содержится по адресу buf1+15. И перемещаем мы 5 элементов, а не 8 как пишется в ECX. Не таким ли должен быть правильный код?:
std
mov edi, buf1+15+5
mov esi, buf1+15
mov ecx, 5
...
Первое -- да,
Первое -- да, повтор, но всё равно спасибо.
Во втором случае мы перемещаем восемь символов -- длина слова "string" и ещё один (как раз таки нулевой). Мы их перемещаем на пять позиций, чтобы было куда вставить слово "long" и пробел.
Здравствуйте, у меня вопрос.
Здравствуйте, Андрей Викторович, объясните, пожалуйста, фрагмент на стр. 90: командой "push ebp" мы сохраняем старое значение ebp (т.е. занесли его значение в esp), а командой "mov ebp, esp" "снимаем" последнее значение esp (т.е. старое значение ebp). Вопрос: не понимаю, зачем нужна вторая команда, ведь уже после первой ("push ebp") в ebp и в esp одинаковые значения?
Спасибо.
тяжело :)
Если вы считаете, что push заносит свой аргумент в esp, то вам следует, по-видимому, внимательно перечитать всё то, что касается стека и команд работы с ним.
Если бы надо было значение ebp занести в esp, можно было бы написать mov esp, ebp. Команда push делает совершенно не это.
Формирование пакетов сообщения
Добрый день, разбираясь с приемом и передачей дейтаграмм, захотелось написать свои протокол передачи данных с контрольной суммой, но возник вопрос как правильно формировать передаваемый пакет, первое что пришло в голову использовать массив с определенной длинной, но получается как-то громоздко или здесь нужно использовать другой подход?
Вам в любом
Вам в любом случае потребуется область памяти, в которой этот пакет будет лежать целиком. Массив — самое логичное решение, хотя, конечно, можно и структуру использовать.
Возможно, я просто не понимаю, в чём заключается ваша проблема, но тогда нужны дополнительные пояснения.
Дополнительные пояснения
На микроконтроллере сделал уст-во и теперь хочу связать его с ПК по UART.
Т.к. на МК мало памяти, то управление с ПК ASCII сообщениями будет осуществляться через собственный мини протокол. Этот протокол будет следующего вида
{start byte, command byte, data 2-byte, crc byte, end byte }.
Со стороны МК есть аппаратный буфер передачи и приема размером один байт. Первоначально я сначала разделил пакеты сообщения на постоянные и переменные. К постоянным отнес те команды которые не требуют передачи дополнительной информации. И передавал их через готовый массив:
После вашей подсказки со структурами код изменил убрав двумерный массив и добавив структуру с массивом:
C переменными сообщениями я решил задачу сразу создав структуру пакета:
Сначала формировал в функции пакет с данными
void make_packet(send_packet* msg, uint8_t* data);
А потом собранный пакет преобразовывал в массив и его передавал:
length_packet = sizeof (send_packet);
uint8_t* raw = malloc(length_packet);
memcpy( raw, &data_packet, length_packet);
Теперь я так понимаю мне не нужно делать преобразования, но не понимаю как отправлять сформированный пакет структуры, не могли бы сориентировать как правильно по байтно передавать структуру и правильный у меня алгоритм передачи?
Со стороны ПК хочется сделать приложение которое из обычных ASCII команд будет формировать готовые пакеты данных для передачи на МК. Программу можно разделить на две части: первая считывает строку и формирует передаваемое сообщение, а вторая часть передача и прием сообщении через преобразователь USB-UART, который отображается в системе как ttyUSB0. Вторая часть мне мутно представляется... Я понял из ваших книг, что в Unix все есть файл, и отталкиваясь от этого надо работать с файлом ttyUSB0, получается что надо использовать функции read() и write(), но не понимаю как правильно передавать и получать данные побайтно и будет ли мешать буферизация, могли бы объяснить алгоритм работы.
На перспективу хочу сделать Lan, закал микросхему у которой есть аппаратный стек протоколов TCP/IP, если я правильно понимаю, то эту же программу немного модернизировав, добавив сокет, можно заворачивать эти же пакеты в TCP и получить уже управление по сети?
Для начала
Для начала всё это не имеет никакого (то есть вообще никакого) отношения к термину, который вы применили в исходном сообщении — здесь нет никаких дейтаграмм. И, коль скоро речь зашла о терминологии, здесь нет никаких ASCII-сообщений, ваш протокол откровенно бинарный. И ещё: никаких преобразователей "USB-UART" не бывает в природе, бывает преобразователь USB-COM, это совершенно не одно и то же - например, UART может обслуживать RS485, а не COM; в любом случае после UART нужен ещё (аппаратный) драйвер того порта, с которым вы будете работать.
Далее, коль скоро речь идёт о микроконтроллере, какая к дьяволу там динамическая память? Сами же говорите, мало памяти, и сами при этом там развозите кучу. И, кстати, я не понял, какое отношение имеют эти три строчки к "передаче пакета" -- взяли уже готовый пакет, выделили память под его копию, скопировали туда... передача-то где? И зачем готовый пакет куда-то копировать? Он же уже готовый.
Что касается работы с /dev/ttyUSB0, прочитайте в третьем томе главу про терминал, дисциплину линии и всё прочее. NB: если Unix'у не сказать, что за com-портом что-то отличное от терминала, он будет уверен, что там терминал. Чтобы общаться с вашей железкой, для начала надо вывести порт из канонического режима. Остальное в книжке написано.
Про сокет вы понимаете неправильно. С чипами, реализующими сетевой стек, я не работал, но что точно могу сказать - никаких сокетов там у вас не будет, сокет - это объект ядра ОС, а на микроконтроллере нет никакой ОС. Со стороны компьютера да, нужен будет сокет, но работают с ним совершенно не так, как с com-портом.
Извините за
Извините за неясность изложения своих мыслей. Постараюсь правильно структурировать их последовательность. После прочтения в третьем томе раздела Сети и Протоколы, я представил, что если нет строго исполнения модели OSI, можно сделать предположение, что написав программный протокол, который будет включать в себя пакеты передаваемые по сети и они будут проверяться при получении, их можно будет передавать и в дейтаграммах и в других физических протоколах, главное чтобы на обоих концах были обработаны эти пакеты. Решил смоделировать и отладить на UDP этот протокол. Написал клиент сервер. Да вы правы протокол бинарный. Конечная цель была перенести его на связать МК с ПК. Задумка его следующая:
При запуске клиента в аргумент передается команда, которая обрабатывается в пакет:
1) Постоянная команда посылает только конкретное действие
./udp_cli light on
udp_srv отвечает сообщением: Turn on the light
2) Переменная команда еще передает параметр
./upd_cli set brightness 50
udp_srv отвечает сообщением: Brightness set to 50
Я приводил примеры по написанному на ПК клиенте т.е. создав сокет, обрабатываем аргументы командной строки и на основании их отправляем сразу или формируем пакет.
unsigned char* parse_command (argv, data_packet);
в функции идет проверка команды, и формируется структура через функцию
void make_packet(cmd_type,data,data_paket);
при формировании два варианта события
1)Если команда постоянная, то просто копируются из массива готовый набор байтов.
2)Если команда переменная, то задается сама команда и через объединение считывается значение передаваемой переменной.
После вашего совета динамику отправил к дъяволу, т.к. только начал переносить код на МК, я сомневался как передавать структуру в массив, потом попробовал напрямую, т.к. структура это тот же массив, единственное сомневаюсь, прямое преобразование может как то плохо влиять к примеру
unsigned char msg[]= (unsigned char*)data_packet;
Где то у пиндосов прочитал, что это деприкайтед, и желательно использовать сериализацию... с ней еще не разбирался.
Потом пакет отправляется серверу и там происходит проверка пакета через функцию
void check_packet(message);
После проверке целостности пакета идет разбор самого пакета
void parse_packet(message);
В функции идет сравнение байт и определение посланной команды, здесь я попробовал применить мапирование, добавив к структуре команд еще литеры сообщений. Когда команда определена, сервер обратно посылает клиенту сообщение String с текстом что выполняет команда.
При написании думал, что знаю указатели но что то запутался, не могли бы пояснить в чем различие, создали тип структуры control_command; после создаю массив структур типа control_command, в чем разница
const control_command array_command[];
от
const control_command* array_command[];
По идеи второй вариант должен размешать массив структур в поле текста как литеры, как я предполагаю.
И второй момент, который я не совсем понимаю, как правильно передавать из массива структур значение в символьный массив.
typedef struct {
char* message[20];
}cmd;
cmd command;
char msg[20];
Через стандартную библиотеку работает
snprintf(msg,20,"%s",*command.message);
сообщение отсылается клиенту, а если пытаюсь назначением
msg=*command.message;
то присылает пустое сообщение. не могли бы пояснить этот момент. Т.к. стандартную библиотеку не хочу использовать.
За дисциплины линии благодарю, попробую всё как вы сказали.
Я как раз подумал, получившийся клиент, не много допилить добавить флаг, который будет выбирать между созданием сокета или управлением линии связи с терминалом.
Теперь по поводу МК. Получившуюся серверную часть udp_srv, я как раз хочу перенести на МК и максимально упростить, чтобы на полученные команды, выполнялись действия. По поводу аппаратного стека, которая будет реализована на микросхеме, там точно также реализована модель сокета, как вы описывали в книге.
Позвольте вас поправить, "USB-UART" существует в природе, это преобразование кодированного сигнала NRZ в кодированный сигнал NRZI. Доказательство тому микросхема CP2102. UART был разработан в AT&T Bell Laboratories для подключения PDP-1 к телетайпу, задолго до появления RS-232. USB-COM является преобразованием кодированного сигнала NRZI в биполярное кодирование сигнала. На ПК использовались микросхемы 8250 UART, которые как вы правильно сказали аппаратным драйвером переводили кодированный сигнал NRZ в биполярный кодированный сигнал RS-232, который более устойчив к электрическим помехам.
Подскажите, а куда можно прислать вам свои код, чтобы вы посмотрели?
Что-то тут всё совсем плохо
Я вам настоятельно рекомендую изучить сначала язык Си -- не микроконтроллеры, не сокеты, а сам язык Си, которого вы совершенно не знаете. А судя по тому, что вы упорно путаете массивы с адресами, я бы даже посоветовал начать с Паскаля. (Нет, массив и адрес -- не одно и то же, это совершенно разные сущности, в том числе и в Си, но начав с Си, вы этого рискуете так и не понять).
И ещё раз повторяю: UDP не имеет и не может иметь никакого отношения к связи с микроконтроллером, тут нельзя ничего "смоделировать", это просто абсолютно разные вещи.
Присылать мне свой код не надо -- мне совершенно не интересно его смотреть.
Андрей
Андрей Викторович,очень благодарен вам, за то что указали мне на мою ошибку. Перечитал указатели и массивы, понял что я пытался копировать указатель, а нужно было литеры. Разобрался написал функцию, всё заработало.
Я вас понял, буду лучше изучать СИ, аж стыдно, такую ерунду писал...)
Не могли бы пояснить, что не правильно сделал, почему ругается Clang в серверной части программы.
Сама функция
Выдает предупреждение
Я понял, что передаются разные типы, и что функция принимает адрес, а sizeof выдает значение, но при этом всё работает, как правильно передать размер?
Увы, ни черта вы меня не поняли
Тут у вас, конечно, всё элементарно, а только я вам принципиально не скажу, "как надо". Вы на мой сайт пришли за рекомендацией? Ну так вот вам моя рекомендация: идите изучайте Паскаль, программируйте на нём, и пока не будете (на Паскале!!!) уверенно обращаться со списками и прочими динамическими структурами данных, пока не будете, опять же, УВЕРЕННО пользоваться var-параметрами и чётко понимать, когда они нужны, а когда нет, пока не достигнете полной уверенности во всём, что касается переменных, адресов, передачи параметров и т.п., пока не напишете две-три программы размером в три-четыре тысячи строк каждая, даже не думайте о возвращении к Си.
Прекрасно понимаю, что мой совет вам может не понравиться, но лично я вам более ничем помочь не могу. Когда человек с вашим уровнем понимания происходящего хватается за компилятор Си, это для окружающих ещё опаснее, чем небезызвестная обезьяна с гранатой. Поэтому или следуйте моему совету, или идите за советом куда-нибудь ещё.
NB: если дальше будете здесь пытаться задавать вопросы по Си, то ваши дальнейшие комментарии не пройдут премодерацию. Я утверждаю, что к изучению Си вы не готовы, и это моё последнее слово по данному вопросу.
Здравствуйте,
Здравствуйте, не понимаю принцип подсчета адреса ячейки из темы "3.2.5. Косвенная адресация; исполнительный адрес", а именно, на стр. 53 адрес вида [matrix+eax+4*ebx] должен указать точный адрес нужного элемента массива. Но если 1-е число 1-й строки находится по адресу matrix+4, 2-е число 1-й строки - matrix+8, 15-е число 1-й строки matrix+60, 1-е число 2-й строки matrix+64 и т.д., то адрес указан неверно. Ощущение такое, что я ошибаюсь в представлении о подсчете адресов. Объясните, пожалуйста.
Спасибо.
Странная проблема
Я думаю, вам станет изрядно проще, если вы будете нумеровать элементы массива с нуля, а не с единицы. Самый первый элемент самой первой строки (то есть имеющий индексы 0,0) находится по адресу
matrix
(без всяких добавлений), следующий элемент той же строки (т.е. с индексами 0,1) находится по адресуmatrix+4
; по адресуmatrix+60
у нас будет начальный элемент следующей строки (то есть элемент с индексами 1,0), ну и так далее.Буфер ввода-вывода
Столкнулся с некоторым недопонимаем, работы с буфером ввода-вывода, при использовании scanf(), в буфере остаются еще какие-то данные, при использование fflush(stdin) они не сбрасываются и если следом за этими командами использовать getchar(), то он их считывает. Возникают вопросы, исходя из этого, получается что в системе используется несколько буферов, один буфер библиотеки stdio, а другой буфер ядра, правильно я понимаю? И как тогда правильно очищать буфер ввода?
Странная формулировка
Теперь по существу. Цитирую документацию:
Такое может потребоваться, например, если вы решили по-тихому поменять поток, открытый на дескрипторе того же stdin'а (например, сделать
dup2(fd, 1);
)— чтобы в буфере не оставалось ошмётков от старого потока. Использовать же fflush(stdin) после scanf, чтобы потом читать из того же потока — по меньшей мере странно: в общем случае вы не можете (никак!) предугадать, до какого места библиотечные функции успели дочитать данные из потока. Если нужно отбросить все данные, например, до конца строки — getchar вам в руки.Взаимодействие с виртульной ФС
Добрый день Андрей Викторович, спасибо большое разобрался, написал функцию:
Теперь возник другой вопрос, на ноутбуке есть функция яркости дисплея, установлена оболочка LXDE, оконный менеджер openbox, в котором прописал скрипт, который привязывает Fn клавиши регулировки яркости к моей программе, теперь суть самой программы:
1. Она считывает информацию из файлов
/sys/class/backlight/acpi_video0/max_brightness
/sys/class/backlight/acpi_video0/brightness
2. Из первого файла она считывает максимальное возможное значение, а из второго текущее значение и изменяет его при нажатии на клавиши регулировки.
3. У меня всё это корректно работает только когда я задаю через root права 666 на файл brightness.
Я считаю это не совсем правильным, и я не понимаю, как моей программе нужно получить доступ для редактирования файла brightness. Программа должна состоять в какой-то группе или при выполнении программы нужно создавать процесс с подменной uid ?
Ага
continue
в теле цикла не нужен, тело можно оставить пустым (оставить одну только точку с запятой или пустой блок {})По поводу прав — программа состоять в группе не может, в группе может состоять пользователь. Скорее всего, на этих файлах уже какая-то группа есть, специально предназначенная для этой цели — вот добавьте своего пользователя в эту группу (редактированием /etc/groups), а права этим файлам поставьте 664 или 660. Правда, они при перезагрузке, скорее всего, будут слетать, для этого тоже есть штатное решение, но оно зависит от дистра и используемых в нём демонов, и к тому же я его всё равно не помню.
Если файлы в группе root, то придётся создать самостоятельно группу под эту задачу, а к файлам применять ещё chgrp или chown.
Заработало!
Всё получилось, спасибо большое, разобрался, система Debian, сделал, как вы сказали правило backlight.rules добавил SUBSYSTEM=="backlight",RUN+="/bin/chgrp video /sys/class/backlight/%k/brightness"
SUBSYSTEM=="backlight",RUN+="/bin/chmod g+w /sys/class/backlight/%k/brightness"
Наверно еще лучше было написать модуль ядра, минуя эти лишние телодвижения, получается при запуске задавать начальное значение и считывая состояние батареи, код fn клавиш, сразу менять значение переменной. Жалко, что в 3 томе у вас примера написания нету.
Модуль ядра для
Модуль ядра для таких целей напоминает расстрел стаи воробьёв стратегической ракетой.
Опечатка?
Здравствуйте!
Не могу понять, есть ли ошибка в томе 2 на странице 75?
Во-первых, ранее в книге говорилось, что при использовании loop в ECX задается число итераций, а у нас их тут должно быть, если я не ошибаюсь, 16 (обратиться к каждому элементу 0..15 и занести туда 0).
Во-вторых, если представить что для элементов 2..15 цикл уже был выполнен, то сейчас в ECX - 1; затем мы заносим в элемент с индексом 1 ноль, команда loop уменьшает ECX на 1 и, т.к. там уже 0, больше никуда не прыгает.
По всему получается, что должно быть 16.
p.s. Большое спасибо за ваши книги!
абсолютно верно
Там действительно должно быть 16, причём эту опечатку до вас никто не заметил. Спасибо!
И кроме того
И, кажется, простой заменой 15 на 16 тут не обойтись, ведь тогда в адрес будут подставляться числа от 16 до 1, а нас устраивает как раз то, что было...
Верно
Да, вы правы. Этот фрагмент в книжке — результат некорректной замены dec/cmp/jge на loop. То есть 15-то там было правильно поставлено, но после замены на loop всё разом стало плохо :)
Размер машинной команды
Подскажите пожалуйста, а как процессор узнает, сколько ячеек памяти ему нужно считать по адресу, который находится в указателе инструкции, чтобы выполнить команду, учитывая, что они могут иметь различный размер?
тут есть несколько уровней понимания
Самый простой ответ — что размер команды можно определить по первому байту. Интересно, кстати, что ответ вообще-то не вполне верный, если учесть существование префиксов вроде REP или модификаторов битности, но можно упереться рогом и заявить, что, мол, модификаторы сначала все по одному считываются, а потом из первого байта становится ясно, сколько всего ещё читать.
В реальности, разумеется, процессор не станет таскать из памяти байты по одному. Фрагмент машинного кода, который вот прямо сейчас в работе, лежит, разумеется, в кеше L1, так что команду даже и считывать-то толком не надо, L1 работает почти с той же скоростью, что и регистры.
Но есть и следующий уровень понимания. Вообще-то современные процессоры имеют так называемый "конвейер команд" — в работе одновременно находятся несколько (вроде бы шесть, хотя тут могу ошибаться) команд на разных стадиях исполнения. Естественно, для этого они все давным-давно должны быть "считаны" (я не знаю, копируются ли они куда-то "ещё ближе", чем L1, но если копируются — то да, их все нужно туда слить) и можно, следовательно, сплошной поток байтов машинного кода разбить на отдельные инструкции на стадии их дешифрации — так что, когда дело доходит до исполнения очередной команды, она уже не только считана, но и дешифрована и всячески подготовлена.
"В первые два
"В первые два "слова" будет занесено число 1, в третье слово 2, в четвертое - число 5 и т.д." Последовательность 1, 1, 2, 3, 5, 8, 13, 21, значит четвертое слово 3 (стр 47)
Уже знаю
Уже знаю про эту опечатку, но всё равно спасибо за внимательность :)
Ошибка в имени регистра
В примере к строковой команде scasb используется регистр esi, хотя надо edi.
В описании команды всё ок.
точно
это уже нашли, но всё равно спасибо :-)
Вызов функции
На странице 231 написано, что в C операция вызова функции является арифметической операцией.
Что под этим имеется в виду?
Имеется в виду
Имеется в виду буквально это: круглые скобки, заключающие в себе список параметров (возможно, пустой) синтаксически представляют собой постфиксную (т.е. стоящую после аргумента) арифметическую операцию.
На стр. 326
На стр. 326 написано
last = first->prev
, хотя по идее должно быть написаноlast = last->prev
.Следующая опечатка находится на той же странице:
free(first->last)
, но должно бытьfree(first->prev)
И пользуясь случаем хочу спросить, какую литературу посоветуете по ассемблеру после изучения вашей книги?
Спасибо!
Обе эти ошибки ещё никто не заметил :) Так что большое спасибо.
Что касается ассемблера — мне кажется, после моей книги само ощущение, "как это делается", должно более-менее сформироваться, так что дальше уже нужны справочники — по системе команд и по конкретному ассемблеру. Это если зачем-то программировать на асме дальше. Но, на мой взгляд, программировать на асме не нужно, достаточно понимания, как это происходит.
Понимание "как
Понимание "как это делается", после прочтения вашей книжки действительно сформировалось, за что вам большое спасибо. Просто появилось желание "попрограммировать"(не знаю, существует ли это слово, но в данном случае оно подходит лучше всего) на "голом" железе.
Есть, конечно
по-английски on bare metal.
Только ассемблер-то тут при чём? Пишите на Си :-)
Опечатка
Не знаю писали уже или нет но мне кажется опечатка в коде на странице 288 (второй том, 4.6.3. Работа с текстовыми файлами). Там написано *count++ , инкрементируется указатель, возможно автор хотел написать (*count)++ , или может дело в версий компилятора, но у меня только так заработал :)
В целом книги автора мне очень нравятся, нравится стиль изложения, спасибо большое за ваши труды
Большое спасибо
Крайне неприятная опечатка, и самое интересное, что вы её заметили первым.
опечатка discr вместо discrim
2-й том §4.2. стр.210
вместо d = discr(p, q, r); должно быть d = discrim(p, q, r);
Спасибо
есть такое, причём вы первый, кто заметил. В архиве примеров (файл qe.c) этой ошибки нет.
По поводу ошибок
По поводу ошибок 2-го тома:
380 страница, ссылка на параграф о максимальной длине строк из первой книги неправильная. Написано §2.15.14, а должно быть §2.15.4.
Есть такое
LaTeX позволяет кросс-референсы по именам, в том числе и между разными документами; но к тому времени, когда писался этот параграф, первый том был уже издан, править его рукопись мне не хотелось, так что на параграфы, к которым в первом томе не были предусмотрены метки, я ссылки ставил вручную. Вот, собственно, и наблюдаем результат ;-)
По поводу
По поводу ошибок 1-го тома:
→ 374-ая страница,
→ 3-ий по счёту пример на этой странице,
→ надо добавить в условие первого цикла проверку first <> nil,
иначе так можно разыменовать nil... :-)
Спасибо!
Да, факт. В своём экземпляре я это пометил, если когда дойдёт дело до переиздания — исправлю. Спасибо!
Столкнулся с
Столкнулся с программой, которая упорно не желает отдавать на конвейер свой стандартный вывод. Попытка получить этот самый вывод с именованного канала тоже эффекта не имела. Между тем, работая "сама по себе", программа, разумеется, текст на консоль выдает. Исходника нет, только бинарник. Команда strings показывает обычные printf'ы. И тем не менее!
Пришлось вернуться к учебникам. В том числе, к вашему. Явно какие-то пробелы со стандартными потоками или настройками терминала (перепробованы все возможные терминалы системы). И уже первый же эксперимент со стандартными потоками привёл к непредсказуемому результату:
Тыц!
То есть, вызывая syscall 4 с параметром 0 (i = 0, STDIN_OUT), почему-то вижу строку в консоли. А, вроде, не должен... Дальше - всё нормально, i = 1, i = 2 - оно и должно выводиться, больше - нет. Но почему выводится при i = 0?
Ну Семёёёёёёёён семёёёёёёныч
догадаться никак?
Это же (очевидно) один и тот же поток, ваш лидер сеанса открыл терминал на чтение и запись и потом dup'ом (или dup2) скопировал в трёх экземплярах -- это самая очевидная и простая реализация старта сеанса на терминале. Закладываться на это, разумеется, не надо: лидер сеанса совершенно не обязан действовать именно так, и далеко не все эмуляторы терминалов юзают такой мерзкий хак, но -- им этого и не запрещает никто.
Что касается той программы, которая не перенаправляется -- см. функцию isatty :-) Если очень надо, напишите программу, которая создаст виртуальный терминал (в третьем томе это будет, в первых двух, увы, нету) и делайте с ней вообще что хотите.
А вообще таких козлов, которые делают вот такие вот бинарники, надо даже не отстреливать, а убивать долго и мучительно.
upd Кстати, кажется, можно и проще: посмотрите на программу screen, она, кажется, имеет встроенный логгинг, при этом запущенные под ней программы про него ничего узнать не могут.
P.S. Глянул ещё раз на ваш исходничек... самое страшное там -- пляски с STDOUT_FILENO. Сам факт их наличия демострирует проблемы настолько капитальные, что я бы вам советовал с них начать, а не с системного вызова.
Не компилируется пример
т2 стр. 211
"...Нужная библиотека называется "m", а подключается она указанием флажка -lm"
Далее пример:
$ gcc -Wall -g -lm qe.c -o qe
НЕ КОМПИЛИТСЯ И ВЫДАЕТ ПРЕЖНЮЮ ОШИБКУ, ПОТОМУ ЧТО:
$ gcc -Wall -g qe.c -o qe -lm
Юзаю Linux ubuntu + gcc
В принципе это
В принципе это логично, поскольку в опциях линкера (по причинам, описанным, например, в параграфе 3.7.6) подключение библиотек должно стоять после объектных файлов. Иной вопрос, что:
Т.е. эта логика, наконец, допёрла до создателей gcc только к пятой версии, а на всех версиях до четвёртой включительно работало в том числе и так, как написано в книге.
Забавно! :)
К примерно таким же последствиям, что и у коллеги, оставившего #93 пост, привело чтение двухтомника и у меня. Тоже пришла мысль проверить квалификацию персонала. Но поскольку пришла она совершенно независимо, то база для контрольных вопросов по языку Си была взята из книги Хезфилда и Кирби. И очень было интересно наблюдать, как человек, вроде бы, уже имеющий достаточный опыт, совершенно искренне не понимает, почему код вида:
выводит-таки "Hello, world"! Налицо явный пробел - при обучении не было акцентировано внимание на тот факт, что в Си "ВСЕ И ВСЕГДА" передается только по значению. А скорее, сыграло роль бессистемное чтение различного рода макулатуры по С++. Вот вещь элементарная, вроде, но в какой-то момент это непонимание может сыграть очень дурную шутку. Человек уже не учится, человек уже работает!
Кстати, в процессе вопрос возник о реализации сиплюсплюс-подобной ссылки. В простейших случаях, это, разумеется, несложно - раздаем направо и налево указателям звезды с амперсандами, и всего делов! Получатся эдакие указатели "брежневского вида". Сложнее несколько будет выстроить typedef'ы, призванные упростить такую запись. Но как бы это сделать "попроще" и "покрасивее"? И как это было сделано в компиляторах С++? "Первокомпайлер С++" Страуструпа, так и вообще, если память не изменяет, переводил плюсовой код на Си... Можно ли там отыскать нечто полезное, позволяющее использовать сиплюсплюс-подобные ссылки именно на Си?
В чистом Си
В чистом Си ссылок нет, только указатели (спасибо Капитану Очевидность). Ссылки -- это синтаксический сахар, введённый именно что языком Си++, и если в языке их нет -- то, значит, нет.
Кстати, на мой скромный взгляд, ссылки -- одна из самых полезных концепций, придуманных Страуструпом. Вынести понятие "леводопустимого выражения" в систему типов -- это дорогого стоит. Но то "плюсы", а это чистый Си, здесь с синтаксическим сахаром беда.
P.S. вы человеку этому ещё задайте вопрос, почему если s[2]='r' сделать, программа в тыкву превратится. И почему этого не произойдёт, если вместо *s в описании написать s[]. И какие возможности при этом потеряются. И почему. А потом отберите у этой обезьяны гранату в виде Си и дайте ему что-нибудь не такое опасное, PHP какой-нибудь.
Вынести
Вынести понятие "леводопустимого выражения" в систему типов -- это дорогого стоит.
А что мне может дать именно леводопустимость ссылки? Как её можно использовать?
Ссылки как формальные параметры - тут, вроде, всё понятно. Можно вернуть ссылку из функции. Её чему-то присвоить. Но собственно-то ссылку зачем мне менять? В каких случаях мне это может понадобиться?
Видимо, у вас
Видимо, у вас таки бардак в голове. Саму ссылку изменить семантически невозможно (за исключением гнилого трюка со ссылочным полем структуры/класса). И, разумеется, не нужно. От неё ведь даже адрес взять невозможно, поскольку над ссылкой вообще нет "своих" операций. Моя фраза про леводопустимость, разумеется, не имела и не могла иметь никакого отношения к изменению самой ссылки как таковой.
Да, конечно! :)
А что касается "обезьяны", то у нас на фирме занимаются исключительно разработкой, где Си уготовано далеко не первое место. Просто требованием при приёме на работу является специализированное высшее образование. Думаю, что согласитесь, что язык Си обязательно должен входить в арсенал любого программиста, чем бы он не занимался. И он, разумеется, входит во все вузовские программы. Просто хотелось бы иметь ДЕЙСТВИТЕЛЬНО грамотных специалистов. А потом, чем чёрт не шутит, может, и другие направления деятельности появятся.
Так что, все "обезьяны" у нас пишут практически исключительно на PHP :)
Просто повторюсь, хотелось бы иметь ДЕЙСТВИТЕЛЬНО грамотных специалистов. С ДЕЙСТВИТЕЛЬНО высшим образованием. А какое высшее образование может быть без уверенного знания архитектуры и, соответственно, Си? Дело именно в этом.
Хорошие у Вас
Хорошие у Вас книги, Андрей Викторович.
Тут уже отметили, что имеется чёткая ориентация на ПОНИМАНИЕ того, что люди делают. Это заметно как по книгам, так и по комментариям и ответам на сайте. Недолго думая, среди программирующего персонала провели лёгенький (типа, "неформальный") опрос. Вопросы взяли из древнейшей "Хрестоматии" Богатырёва. Так подавляющее большинство (а это уже не студенты) не смогло ответить на элементарные вопросы. Пишут люди, правда, не под Unix, но ведь не секрет, что MSDOS, откуда пришли современные поделия от Майкрософт, представляла собой именно изрядно кастрированный Unix! Да и ветку NT ваяли именно спецы от Unix. А откуда бы им ещё взяться, "спецам-то"? Самостоятельно же свой Xenix (или как он там назывался) Майкрософт когда-то так и не вытянула. В общем, (типа, "неформально") было предложено провинившимся пройти указанную книжку всем "затруднившимся". К примеру, под (тоже информация с этого сайта) интерпретатором Ch. Достаточно вполне. Ну, а попавшимся же вторично на откровенном незнании элементарных вещей, кроме уже вполне "типа, формального" увольнения ничего не светит.
Так что, в хвост и в гриву студентов, Андрей Викторович, в хвост и в гриву! Во имя добра. Да и их самих тоже!
Все ли функции нужны?
Здравствуйте, Андрей Викторович!
Читаю ваши книги. Не столько в целях ознакомления с основами программирования, сколько из соображений упорядочивания, систематизации и усугубления. Что-то изменяется, что-то просветляется, появляются какие-то вопросы... То есть, процесс затыкания дыр в имеющейся "личной парадигме программирования", так скажем. Одновременно просматривается стандартная библиотека. Нарвался на никогда не использовавшуюся мною функцию strdup(). Пытаюсь понять, зачем и в каких случаях она могла бы мне понадобиться - кто же писал ее зачем-то! Так вот, пока понять не могу, что она мне может дать вместо обычно применяемой strcpy(), кроме дополнительного выделения памяти и обязанности не забыть ее же освободить? Бывают ли такие случаи?
Нужны, конечно,
Нужны, конечно, далеко не все функции, некоторые даже не просто не нужны, а запрещены -- тот же gets пресловутый. Я бы сказал, что довольно бессмысленна функция sscanf, хотя со мной кто-то, возможно, и не согласится. Совершенно бесполезными мне кажутся fread/fwrite; или вот есть ещё системный вызов creat, я так и не понял, накой чёрт он вообще есть.
А вот strdup я как раз довольно активно использую. Больше того, при работе на C++, чтобы не освобождать delete'ом выделенное malloc'ом, я часто вынужден писать собственную её версию (обычно я называю её strdup_n), которая от оригинала отличается использованием new вместо malloc'а. Разумеется, это примерно то же самое, как вызвать strlen, потом malloc, потом strcpy; ну так и что с того? Вообще любую из функций string.h можно сделать за один, максимум за два цикла, то есть без string.h можно вообще обойтись, но тогда программа будет несколько хуже читаться.
Как вы думаете,
Как вы думаете, имеет ли смысл издать книжку - мануал по стандартным библиотекам си, освещающую эти аспекты? Было бы интересно знать, от чего в коде стоит избавляться. Лучше наверное сразу на английском, озаглавить например "C language best practices". И содержание - каждая библиотека, и по каждой функции расписано где и для чего применять, напротив нерекомендованных - крест и рекомендации как и на что заменить.
?
Для начала хотелось бы понять, что вы понимаете под словосочетанием "каждая библиотека". Думаю, всё более-менее станет понятно, если вы приведёте пример того, что считаете "библиотекой" (называете этим словом).
Прочитал оба
Прочитал оба тома. Вижу, комментарии положительные преобладают явно. Не буду оригинальным — действительно, хорошее введение в программирование на русском языке. Если по чесноку, не ожидалось. Как-то давно сложилась точка зрения, что на русском языке ничего подобного появиться не может, уж тем более в условиях, которые сложились с образованием в нынешней России. Те же основы программирования мной, да и многими моими знакомыми осваивались по (тоже весьма немногочисленным — проблема образования не только проблема России) англоязычным источникам. В первую очередь вспоминаются лекции по C/C++, читаемые в ряде учебных заведений США — используется там достаточно давно для учебных целей интерпретатор Ch. В Европе для целей обучения используется местный интерпретатор ROOT, предназначенный, что интересно, вовсе не для профессиональных программистов, а именно для пользователей. Очень вменяемые люди читали! Многие по сей день просто используют ch-shell даже для административных целей. Синтаксис bash — это, наверно, самая непереносимая на платоформу мозгов, штука. Ну, а язык Си поймет даже туземец из тростниковых зарослей каких-нибудь островов Тристан-да-Кунья. Кстати, та же беда, что и в bash, касается и синтаксиса скриптового языка vim. Сам по себе, редактор просто волшебный, но как только случается придти в голову мысли изменить в нем что-то чуть-чуть нетривиально, тут же с завистью начинаешь поглядывать на emacs. Вот нету в жизни совершенства! Попадались также знатные англоязычные лекции по Ocaml и Scheme. Во всех случаях ощущалась ориентация на понимание обучающегося, что в других (всех остальных!) случаях, не ощущалось совершенно! И это же есть в ваших книгах.
В общем, спасибо за книги, творческих успехов в Новом году (не за горами, а когда еще зайти случиться?). Кстати, и крисмас у вас, смотрю, подходящий! ;) Удачи.
"...с завистью
"...с завистью поглядывать на emacs..."
Не надо "поглядывать с завистью". Использовать надо emacs. Чего там не хватает нормальным людям, так это переключения режимов. Ставим плагин evil, и все - имеем все режимы Vim, всю его клавиатуру. Ну, с небольшими исключениями (которые можно легко при желании поправить) - напр. нельзя ":set num", нельзя перейти в Normal с помощью Ctrl-c, только ECS или Ctrl-[, не работает ":di", только его аналог ":reg", и тому подобные мелочи.
Короче, ставим evil, работаем так же как в Vim, а все функции пишем на elisp. Плохо, что ли? Для меня такой гибрид оказался находкой просто сказочной. Горя не знаю. Vim - редактор замечательный, но VimScript - это даже хуже, чем bash.
Emacs - редактор не менее замечательный, но уродовать там собственные пальцы его исходными биндингами совершенно ни к чему.
Короче, берем отовсюду все, что есть лучшего, и живем счастливо. Есть в жизни совершенство, но приходится иногда поискать.
Ну, и "ctrl:swapcaps", конечно, еще никакому редактору не мешало.
Да, книги
Да, книги написано достаточно понятно и хорошо. Многие вещи, на которые раньше внимания не обращал, обрели какой-то смысл. Не всем доступный метод изложения. Возможно, не настолько эти вещи и важны, но ведь никогда не знаешь, когда эта чертова птичка нагадит на голову только из-за того, что что-то пропустилось мимо ушей или же там и застряло!
Встречал где-то у Вас указание на различные конвенции вызова (Си, Паскаль, и еще некоторые, о которых, возможно, и упоминать не стоит), по разному выравнивающие стек. И разумеется, встречались они многократно и раньше. Так вот, как бы это самое "выравнивание стека" разглядеть получше (например, в отладчике, чтобы понаглядней), а то коан сей как-то не укладывается популярно в голову?
Для начала
Для начала замечу, что конвенции Си и Паскаля различаются уж точно не тем, как они выравнивают стек. Что касается выравнивания стека, то вроде бы в моей книжке про это нет ни слова, если не считать очевидных замечаний о том, что ESP при работе с 32-битным кодом следует всегда сохранять кратным четырём.
Что касается выравнивания стека как такового, то вообще-то его и не надо разглядывать, никого оно не волнует, кроме компиляторщиков (и то сильно не всех). Но если очень хочется, то стоит обратить внимание на то, как тот же gcc оформляет вызовы функций, то есть посмотреть, какой ассемблерный код генерируется, и попробовать это проделать при различном совокупном размере локальных переменных (плюс совокупный размер параметров самой "длинной" из вызываемых функций, под длинной я в данном случае подразумеваю количество параметров). Вот тут, например, народ это обсуждал: http://stackoverflow.com/questions/1061818/stack-allocation-padding-and-...
Читаю второй
Читаю второй том. Вроде, понятным языком написано. Смотрю комменты, вроде, тоже понятно излагается. Вот и задумал вопрос задать незамысловатый, который временами встает, но точного ответа по нему не знаю.
Многократно в течение жизни приходилось читать файлы, парсить их всяко-разно. Преимущественно использовались regex-библиотеки. Вроде, всё хорошо. Разве что, не совсем понятно, как оно работает. Вот, скажем, для начала какой-то regex-шаблон "компилируется" какой-то функцией. Затем имеем возможность использовать целую кучу других функций, использующих уже этот "скомпилированный шаблон". Имеются также более специализированные csv-библиотеки. Тоже, вроде бы, упрощающие жизнь. Правда, чего они на самом деле делают и как - тоже ясного немного...
Так и вопрос - настолько же они эффективны, сколь и непонятны, или как? Ведь зачастую распарсить можно и просто обойдясь каким-нибудь вспомогательным указателем. Во всяком случае, чётко представляешь себе, что делаешь!
Вот к примеру (фантазировать особо не стал) раздербан строк файла /etc/passwd по сепаратору ":" (ну, и вывод для теста с другим сепаратором "|"). Возможно, даже вспомогательный указатель тут лишний, это надо внимательней смотреть. Но суть ясна - двигаем ручками указатель, ставим нули, кусаем строку по частям:
Так и что же получится, если здесь я буду применять какую-либо библиотеку? Будет ли быстрее? В данном случае, кстати, ещё и возможно применение штатной фукнции strtok() (но она мне почему-то никогда не нравилась). Как бы оно, чтобы "эффективней"?
Если интересно
Если интересно моё мнение, то для использования библиотеки (любой) необходимы очень веские причины. Эту библиотеку потом придётся всюду за собой таскать, помнить про неё, заставлять, например, майнтейнеров при сборке вашего пакета сначала ставить в системе библиотеку (когда таких библиотек десяток, хочется авторов пакета немножко убить), и так далее.
Не так чтобы библиотеки вообще нельзя было использовать, в некоторых специфических случаях они могут изрядно сэкономить наши усилия. Вот, например, потребовалось мне недавно md5 посчитать в одной из моих программ. Свою реализацию я бы писал неделю, не меньше; при этом сторонняя (и свободно распространяемая) реализация, как оказалось, представляет собой один модуль на C, я его даже не стал рассматривать как библиотеку -- просто кинул обычным модулем в свои исходники, и всё. Никаких внешних зависимостей, никаких трудностей на ровном месте, просто в моей программе на один модуль (два файла) больше.
Между тем, md5 есть во всяких криптографических библиотеках, которые якобы все из себя такие популярные, везде есть и всё такое. Так вот, ради подсчёта md5 я ни в жисть не стал бы завязывать свой проект на некую внешнюю зависимость, я бы реализацию (если бы та, которая сейчас у меня, не попалась вовремя) либо "выкусил" из какой-то существующей библиотеки, либо вообще сам бы написал. В крайнем случае можно всю библиотеку закопировать в дерево исходников. Но вот внешние зависимости (да и вообще зависимости) -- это абсолютное вселенское зло, их нужно избегать настолько, насколько это вообще возможно.
did you forget -T?
Взялся было за самообразование в области FreePascal'я, да наткнулся то, что fpc по всякому поводу и без оного пишет warning: "/usr/bin/ld.bfd: warning: link.res contains output sections; did you forget -T?". Не то, чтобы оно очень сильно беспокоило, но вообще-то мое процессорное время не для того, чтобы выписывать на мониторе всякую ерунду. Обратился к разработчикам с классическим русским вопросом - "Что делать?". Как выяснилось, это не разработчики, это отморозки какие-то - сказали, что-то вроде "это ничаво, и так хорошо, не беспокойтесь, а была бы другая версия, то там был бы специальный параметр..." Короче, я так понял, что этот "did you forget -T?" просто непременный атрибут именно моей версии этого компилятора.
Попробовал, конечно, убрать - fpc hello.pas 2>/dev/null, но при таком раскладе давятся все warning'и (ошибки, слава богу, идут в stdout). А вот вариация по типу fpc hello.pas|grep -v "did you forget" почему-то не отработала.
Недолго думая, написал вот такую колбасу:
что, вроде, решило проблему, но хорошо, что никто за спиной не стоял и не видел этого извращения. То есть, нельзя ли как-нибудь попроще убрать эту никому не нужный warning? А то к концу книжки же сниться будет! :-)
К концу книжки
К концу книжки этот warning начнёт сливаться с окружающим пейзажем и перестанет бросаться в глаза. Обратите внимание, что я про это паразитное предупреждение упоминаю, см. т.1, стр. 202.
В общем, проблема известная, существует уже лет десять, разработчикам на неё, судя по всему, наплевать. Лично мне она тоже уже давно не мешает. А grep -v у вас не отработало из-за того, что это всё выдаётся на stderr, а grep на свой stdin получает stdout.
Попробуйте вот так:
Да, спасибо!
Совсем другое дело! В .vimrc дописал ко всем своим собакам:
И по @f имею вполне аккуратный вывод:
Как всегда - все просто. Когда знаешь, конечно! )))
То, что я там понаписал ранее, не лезло ни в какие рамы. Даже не по длине - просто невозврат нуля одной из связанных && команд (а такое возможно) нуль в итоге уже не вернет. А компилятор ничего другого возвращать и не должен! Ну, а здесь все четко... Спасибо.
Читаю Вашу
Читаю Вашу книжку, стандартные функции ввода-вывода. В параллель открыто еще несколько. Пробую читать файл fopen/fread/fclose и open/read/close. Думал, будет одно и то же. Однако, нет:
fopen and open
Получается, что не "одно и то же"! Разница в результирующем бинарнике в целый килобайт.
Файл один и тот же, всяко более 256 байт, компилятор один, ключи тоже, ldd выдает одинаковый вывод. Куда дальше рыть? Откуда такая разница? Чем-то плохи эти open/read/write? Еще и сделать-то ничего не успел, а уже килобайт отдай дяде! Куда годится...
Довольно неожиданный эффект
По идее, должно быть с точностью до наоборот, ведь open/read/write -- это системные вызовы, которые и первая программа использует тоже, только неявно, а вот fopen/fread/fwrite -- библиотечные функции, которых вторая программа не использует.
Подозреваю, что при статической сборке вторая программа даст существенно мЕньший бинарник, чем первая. А вот почему так происходит при динамической сборке -- я объяснить не могу.
В общем, думаю,
В общем, думаю, вопрос ушел.
open/read/write - это все-таки системные вызовы. К счастью, в моем файле 256 байт - это целая куча строк. Так под strace видно, что вывод каждой строки fwrite'ом делается отдельным вызовом write с вытекающими. И даже будучи просто запущенной под time, вторая программа работает быстрее.
С панталыку сбил какой-то неутык в компиляторе TCC. Размеры получаемого файла там существенно меньше, но есть и свои "особенности", как выясняется...
Если программы
Если программы те же, что были на иллюстрации несколькими комментами выше, то я бы ещё на вашем месте обратил внимание на то, что сказано по поводу анализа возвращаемого значения read на стр.302 (первый абзац сверху). К fread это тоже относится.
Да, в рабочих-то
Да, в рабочих-то условиях проверять, конечно, надо!
Единственное, что осталось непонятным, так это ПОЧЕМУ функция fwrite() использует системный вызов write на каждую строку? Странная довольно реализация... Вроде, высокоуровневая функция, буферизация какая-то, системные вызовы как-то экономиться должны! И где здесь "экономия"?
Если не врет strace
Разумеется, в случае варианта с write, вызов единственный - сразу выводит то, что просят.
fwrite не
fwrite не буферизует, буферизация действует только на потоковый ввод-вывод, а блочный буферизовать слишком сложно, чтобы это было в библиотеке. Потому никто их и не использует, все для бинарно-блочных файлов используют низкий уровень. fread/fwrite просто бессмысленны.
Вопрос по буферизации
А имеется ли вообще смысл вместо длительной записи в цикле в файл результатов какой-либо обработки, записывать предварительно в специально выделенный для этой цели буфер, а потом одним ударом забабахать весь буфер в файл перед его закрытием? Имеется ли какая-то при этом выгода, или то на то и выходит?
Depends
Как обычно, зависит от конкретики. Если сравнивать скорость записи в файл по одному байту и запись блока, то разница в скорости будет примерно как размер этого блока :-) Имеется в виду, естественно, запись без локальной буферизации, то есть, например, 4096 обращений к write по одному байту или одно обращение к write на запись блока в 4096 байт.
А вот если, скажем, записывать блоками по те же 4096 байт, то разницы с записью целиком мегабайта, скорее всего, не будет -- по крайней мере, заметной.
Больше того, при каком-то достаточно гигантском размере буфера эффект начнётся обратный, поскольку мы с этим нашим "буфером" потеряем на свопинге, то есть получится, что наша информация фактически будет записана на диск дважды, да плюс ещё оттуда же прочитана.
Измерений я не проводил, но если я что-то в чём-то понимаю, то локальная буферизация имеет смысл при размере каждой записи примерно до 256 байт, дальше выигрыш (формально) ещё долго присутствует, но он не стоит того, чтобы ради него городить буфера.
Воистину depends!
Собственно, я не собирался ничего "измерять", так уж, на скорую руку получилось...
Предельно глупая функция копирования файла. Просто читаем построчно первый, пишем во второй в цикле. Все обычно. Работает, естественно, правильно. А далее - собираем всё в предварительно замаллоченный буфер и перед закрытием файла, в него сбрасываем. Ну, или работаем его в стеке. Как, собственно, и хотелось. В надежде получить какой-то эффект. И я его получил! ))) Так мне даже рядом не показалось, что здесь что-то можно было сделать "не так"!
А вот и результат!
То есть, глядя уже на второй исходник, можно было бы предположить, что произойдёт. Но этого я не увидел, просто запустил под time. И далее уже просто поднимал упавшую на пол челюсть! :) Разница в скорости исполнения составила более 500! Можно, оказывается, и так писать на Си. Тем более, поставленная задача была решена верно. Скорее всего, на каком-нибудь Low-Speed-C-Contest я занял бы место не последнее.
В общем, я считаю, что написал я "функцию копирования файла" очень даже не зря.
Мораль сей басни такова - это вам не шахматы, тут думать нужно...
Это не из той сказки сценка
При чём тут буферизация, при чём тут ввод-вывод, вы же всё время, какое можно, угробили сначала strcat'ом, а потом fprintf'ом. На одних поисках и копированиях.
Возьмите fputs вместо fprintf -- думаю, уже за счёт этого получится раза в два быстрее. А потом в цикле, где читаете и копируете, заведите указатель на текущее место в строке и после каждого копирования сдвигайте его на strlen(buf), чтобы не надо было каждый раз весь буфер просматривать в поисках его конца. После этого, подозреваю, скорости если не сравняются, то станут похожими.
Да, конечно!
Правда, при замене fprinf() на fputs() ничего заметного я не получил.
Ну, а смещение начала буфера для записи в цикле, разумеется, сделало результаты уже сравнимыми.
Попытка нашинковать копируемый файл на ровные дольки тоже результат дала.
Так вот оно получилось
Можно было, конечно, еще в один цикл загнать, изменяя размер количества читаемых/записываемых байт и измеряя время, но я по-простецки полтора десятка раз запустил программку под time.
Безо всяких gnuplot'ов картина мира довольно наглядна. Как бы то ни было, выяснено, что временами можно что-то поймать, изменяя размер буфера.
По поводу
По поводу "существенно меньшего бинарника" при статической сборке получилось не сильно.
Действительно, у gcc и pcc размер бинарника несколько меньше как при статической сборке, так и при динамической. (TCC же просто отказался собирать статически. И перекокос в его случае, возможно, просто связан с особенностями самого компилятора и его личной "стандартной библиотеки Си" - libtcc.a). Однако, разница совсем невелика. В исходники, правда, пока не залезал, но что-то появилось устойчивое ощущение, что open()/read()/write() -это "не совсем" системные вызовы, а просто одноименные (может, чуть более низкоуровневые, чем в случае fopen()/fread()/fwrite()) функции-оболочки, сделанные для "какой-нибудь с чем-нибудь совместимости".
Вдогонку.
Уже написал, что введение в низкий уровень хорошее. Однако, вижу тут уже люди во всякие графические приблуды полезли! Плохого в этом ничего нет, инструментарий всякий знать нужно, и дело даже не в том, что edb - это не "unix-way", а в том, что в самой книге отсутствуют даже примеры работы с элементарными утилитами, соответствующими уровню обучающихся, нет никаких рекомендаций по начальным простейшим инструментами. GDB - это довольно сложно, довольно круто и не всегда нужно. В то же время существует куча консольных утилит, хекс-редакторов, отладчиков, в том числе (по-моему, очень удобных) vi-like. Всякие ald, hc, bvi, radare - только сходу вспомнилось! Кучи их. Кстати, очень жаль, что не попадалось 100%-ного аналога dos'овского debug - чудная была игрушка для 16 разрядов. А пужать людей синтаксисом ATT, родного лишь для больших машин, совершенно ни к чему. Уж если NASM, так и все человек должен видеть в нормальном синтаксисе. Вот в этом, по-моему, недостаток книги.
Э?
Знаете, вот вообще ничего не понял из вашего текста. Как сейчас говорят, не распарсил.
Прочитал.
Прочитал. Хорошее введение в низкий уровень. Понятное, доступное. Напомнило куда более раннюю книжку Sivarama P. Dandamudi "Guide to Assembly Language in Linux". Мастхэв. Необязательно даже на книжной полке, в мозгах - однозначно.
Это о другом. В
Это о другом. В книге Дандамунди ассемблер -- самоцель, у меня он никогда самоцелью не был.
Том 2-й,
Том 2-й, страницы 258-259
Приводится пример с инициализацией локального массива.
"... данные будут копироваться в область памяти в стековом фрейме каждый раз в начале выполнения такой функции".
А вот что получается у меня:
Как оно получается
Здесь просто объявляется и инициализируется строка в функции main().
Эта же строка явно проглядывается в результирующем файле безо всякого hexa. Так неужели, если далее будет следовать, скажем, фукнция printf("%s", str), принимающая указатель на эту строку, в стек будет копироваться не указатель, а вся строка? Неужели ее нельзя вытащить из уже загруженного в память файла? Область памяти-то, очевидно, read, как минимум?
Или чего-то не так понимаю?
Как оно
Как оно получается
И что, простите? Да, строковый литерал размещён в самом конце сегмента кода, где он, собственно, и должен быть. Плюс к тому в вашей программе нет ничего даже отдалённо напоминающего случай, про который вы говорите: никаких локальных массивов вы в своей функции main не описываете. Возможно, вам стоит обратить внимание на рассуждение, которое приведено в книге на стр. 265--266.
Так неужели, если далее будет следовать, скажем, фукнция printf("%s", str), принимающая указатель на эту строку, в стек будет копироваться не указатель, а вся строка?
Нет, разумеется. Это вообще невозможно, строка -- это массив, а массивы как целое в Си не обрабатываются. У вас какой-то бардак в голове, но я пока не понял его причину.
Не будет
Не будет бардака, не будет и порядка.
Что же я делаю тогда, как не объявляю и не тут же не инициализирую массив символов? И этот массив находится внутри функции main(). Чем же он не локальный, где я неправ?
Ну какой же это массив, в самом деле
Вы объявляете (точнее, не объявляете, а описываете) указатель, а не массив. Компилятор размещает строковый литерал в сегменте кода, как обычно, а ваш указатель str инициализирует адресом начала этого строкового литерала. В том, что всё обстоит именно так, можете убедиться, попробовав присвоить что-нибудь элементу вашего "массива", то есть сделать что-нибудь вроде str[0]='B'. Программа при этом рухнет с грохотом и спецэффектами. А вот если вместо того, что у вас там написано, написать
то тогда это уже будет локальный массив. Впрочем, строковый литерал в конце сегмента кода будет в любом случае, должна же где-то лежать эта строка; обнаружить, что строка копируется в массив в начале функции, можно, либо дизассемблировав исполняемый файл, либо остановив компилятор на стадии генерации ассемблерного кода (флажок -S, потом смотрите файл с суффиксом .s, только учтите, что там синтаксис AT&T)
Да, разница,
Да, разница, конечно, есть... С этим надо посмотреть внимательней, спасибо.
Однако, в случае именно массива, строковый литерал в коде отсутствует, там что-то куда более невнятное. И как сама машина это разбирает, пока неясно! )))
А синтаксис gcc к "человеческому" виду я привожу в конфиге - .intel_syntax.
Ну, тут уж
Ну, тут уж совсем всё просто. Это "невнятное" представляет собой четыре команды mov, по четыре байта каждая. Действительно, это явно быстрее, чем "честно" копировать строку.
Да, спасибо!Уже
Да, спасибо!
Уже под gdb всяко-разно покрутил, там без всяких вставок интеловский синтаксис легко делается одной строкой в ~/.gdbinit - set disassembly-flavor intel.
Надо сказать, что "вроде бы хорошо знакомые вещи" при ближайшем рассмотрении оказываются не такими уж и знакомыми. Во всяком случае, как бы смотришь с "другой стороны".
Написано хорошо у вас, на понимание ориентировано... То есть, не зря книжку взял.
Да, все верно
Да, все верно написано относительно стека и массива.
Вот наиболее наглядно из того, что смотрел
В первом случае (char str[]= "very long initialization";) строка, действительно, полностью копируется в стек, собака!
Во втором (char *str = "very long string";) - в стеке только указатель.
Получается, что str[] = "very long initialization" запись неверная в принципе.
Я так, правда, никогда и не делал, но теперь уж точно не буду! :-)
А у вас ус
ус отклеилсяссылка битая :-) Запись я бы не стал называть "неверной в принципе", бывают случаи, когда именно такой вариант играбелен --- например, в случае, когда строка представляет собой некое сообщение, в котором в зависимости от параметров функции нужно сначала что-то "подкрутить". Кроме того, если переменная глобальная или локальная-статическая, то никакого копирования не будет, а строка окажется в сегменте данных, при том что синтаксис ровно тот же. В общем, пользоваться инициализацией массивов нужно, просто надо при этом понимать, что происходит. Это, впрочем, при работе на Си обязательно всегда.Действительно, отклеился! :(
Ох, уж мне этот клей от Майкрософт...
По вышеподсказанному EDB отлично видно, ЧТО находится в стеке при str[] и *str.
Хотя, если бы кто раньше мне сказал, что строка в принципе может копироваться в стек, я бы в него непременно плюнул, и сейчас мне было бы стыдно. Полагалось, что для изменяемых строк существует выделение памяти в куче, и только. Оказывается, дело обстоит несколько иначе - если в строке не целый роман, то можно и в стеке - побыстрее должно быть, однако!
При чём тут
При чём тут "побыстрее"? Локальные переменные, будь то хоть маленький char, хоть огромный пятимерный массив, заводятся в стеке. Их, собственно говоря, больше негде заводить, ведь в языке Си (самом по себе) нет никакой кучи и никогда не было. Функции malloc и free — библиотечные, компилятор о них не знает, а если и знает, то должен делать вид, что не знает.
Мораль той басни исключительно в том, что нужно двадцать раз подумать, прежде чем создавать локальный массив.
Да, спасибо!
Да, спасибо! Бардака, вроде, поменьше стало...
Правда, поначалу смутили несколько "огромные пятимерные массивы" в стеке, поскольку ulimit -s у меня показывает 8192К возможного стека... Ведь, ежели вдруг еще внезапно приспичит рекурнуться (а мало ли?!) с эдакими массивами, тут же поимеем SIGSEGV по морде!
Но оказывается, даже и на такой космический случай имеется волшебная структура rlimit из файла sys/resource и волшебная же функция setrlimit(), с помощью которых можем стек и увеличить. Во всяком случае, повторный вызов функций getrlimit(RLIMIT_STACK, &stack) и system("ulimit -s"); показывают уже запрошенные размеры стека. Плюс, говорят, имеется для той же цели еще какой-то ключик у gcc. То есть, не все так плохо, на самом деле.
Насчёт огромных пятимерных
Если в итоге массив в стеке не поместится, то, стало быть, судьба его такова. Программа при этом, скорее всего, будет валиться с треском в самом начале функции, в которой такой массив описан. Это никак не отменяет того факта, что компилятору просто негде больше размещать объекты, описанные как локальные в функциях. Программист сам может разместить объект в куче, но для этого ему потребуются инструменты, о которых компилятор просто не знает.
Ну, что можно
Ну, что можно сходу сказать, так это то, что если люди из-за одной строки лезут в hex с отладчиком, то книга методически написана верно, и уже не зря. Даже из-за одной строки залезть в отладчик стоит, посмотреть - как там чего. Да и изначальная ориентация на *nix верна абсолютно - сама система как бы предназначена для обучения - три ведра с горкой уже предустановленных утилит, при некотором желании не оставляющих шансов на то, что какие-то непонятки в полученном бинарнике могут остаться. Синтаксис интел (ATT только для тех, специализируется на ассемблерах) всеми ими поддерживается с незапамятных времен. Хочешь - их пользуй, хочешь "покрасивше" - ставь какой-нибудь EDB (тот же Olly) - и дизассемблер, и hex тебе тут, и регистры, и стек, и чего хочешь - короче, было бы желание учиться...
Да, "учебным"
Да, "учебным" языком Си назвать действительно сложно. И первым языком он, как и С++, быть не может никак, тут никто не спорит. Но в какой-то же момент он является "учебным" - именно его и изучают. Так вот, дело именно в том, что после его изучения, у многих обучавшихся тут же возникает желание освоить какой-то другой язык, предоставляющий возможности, которые в Си могут быть получены только подключением соответствующих библиотек. Саму же среду, в которой оказывается человек уже после изучения начал Си, достаточно дружелюбной назвать нельзя. Она требует несколько более высокой квалификации. А навыки сходу не приходят. Именно поэтому, мне чрезвычайно импонировало включение в Вашу книгу рассмотрение библиотеки ncurses. То есть, "хочешь - найдешь", было бы это желание "что-то найти". А скорость исполнения языка, его простота делают его пригодным не только для профессиональных целей. То есть, некоторый шаг навстречу пользователю, изучавшему Си, все-таки нужен. Дать ему основы дальнейшего существования вместе с Си. Оно того стоит.
Относительно того, что видеокурсы являются "суррогатом" и "самообманом", согласиться не могу никак. Это один из способов обучения. К "социальным сетям", ютубу, "самому ГУГЛ" отношусь вряд ли лучше Вас. Например, в качестве поисковых систем уже давно используются нигма и дак-дак. Гугл - в запасе. Но пользователи-то здесь причем? Если пользователь посещает ютуб, использует его для хостинга, каких-то других целей, это еще не означает, что он ютубнутый на всю голову! И потом, книги-то Вы издаете! А если порыть в этом издательстве, не найдется ли чего из того, почему к тому же Гуглу отношение у многих не самое лучшее?
Впрочем, здесь, конечно, хозяин барин. Это было лишь сожаление...
А если порыть в
А если порыть в этом издательстве,
Издательство, с которым я работаю, специализируется на издании книг за счёт их авторов или спонсоров. В копирастии они вроде бы не замечены. А в попытках подмять под себя весь Интернет, как это делает Гугл -- тем более. Так что там хоть рой, хоть не рой.
Из двухтомника
Из двухтомника видела только том второй. Часть, посвященная ассемблеру прошла по диагонали. Что же касается части, освещающей начала Си, то это, пожалуй, одно из наиболее толковых введений в предмет, которые случалось видеть. Особенно порадовало краткое описание библиотеки ncurses. Дело в том, что после самого начального обучения, человек остается один на один со своими проблемами - чисто средствами языка решить какие-то свои даже элементарные проблемы он не может, тут нужен уже навык поиска соответствующих библиотек, коих хоть и написано немало, но без такогого навыка, простейшим путем является поиск языка, в котором возможности этих библиотек являются встроенными. В результате язык Си воспринимается чисто как учебный, "теоретический", несмотря на то, что он как был, как и останется живым, и далеко еще не "полтора десятка лет". Нету ни малейшего намека на то, что ему грядет какая-то замена.
На днях буквально случилось столкнуться с ситуацией, когда обучающийся (основы Lisp), снес рекомендуемый sbcl и заменил его на CLISP только на том основании, что в sbcl нет поддержки readline. То ли в силу лицензионной GNU-тости, то ли просто пакет был так скомпилирован. Установка оболочек readline, таких как rlwrap, rlfe решило проблему тут же. То есть, дело именно в отсутствии элементарных знаний, что ВООБЩЕ существует в природе, в том числе и для Си. А существует немало!
Ясно, что в книгу такого объема вместить удается лишь самое необходимое, возможно, те, кто у Вас обучается, на живых лекциях получают больше. Однако, очень жаль, что порыв на ютубе по слову "Столяров", не получилось отыскать ничего подходящего. Лучшим, что случалось видеть из русскоязычного по началам Си - это "специалистовский" несколькодневный курс Тетерина. Всякие скринкасты и видеоуроки, думаю, дело не очень благодарное, но, все же, было несколько жаль. Это даже не призыв к какой-то "рекламе", хотя в данном случае, это было бы вполне обоснованно, а именно просто сожаление, что вживую преподаваемые (уверена - хорошо) начала Си - да и не только Си, важна методика - остаются достоянием от силы лишь нескольких студенческих групп.
Ну, про язык Си
Ну, про язык Си -- вот уж чего ни разу не видел, так это чтобы его называли "учебным". Как раз для обучения он категорически не годится, при этом он продолжает удерживать первое место по общему объёму работающшего (используемого) кода, на нём написанного.
К сожалению, должен сказать, что ни на ютьюбе, ни на любых других сервисах компании Google от меня вы не найдёте ничего и никогда, ни сейчас, ни в будущем. И в так называемых "социальных сетях" тоже -- во всяком случае, пока они не станут реально сетями, распределёнными и не принадлежащими никому в виде целого (а этого в обозримом будущем не произойдёт).
Ну и ещё один момент. Видеозапись лекции -- это совершенно не то же самое, что лекция "вживую". Если прослушать лекцию вживую, пожалуй, всё же гораздо интереснее, нежели прочитать материал в книге, то просмотр видеозаписи лекций не выдерживает с книгами никакого сравнения. Я не вижу для себя никакого смысла вообще создавать видеозаписи своих курсов, всё это не более чем суррогат и самообман.
Тоже читаю
Тоже читаю первый том. Указатели, динамические структуры. Языком понятным написано, не вредная совсем у Вас книжка.
Но по ходу чтения пришлось вернуться и к вещам более статическим. Задача предельно простая - имеется файл (с полмиллиона строк) в формате CSV, на каждой строке пара ключ-значение. Из него нужно выдрать все данные и поместить в удобную структуру, постоянно висящую в памяти. Сходу просматривается примитивная сишная структура из двух строк. Ну, можно int и char[], только позже из этих ключей-значений будет формироваться URL, так что, два char[], чтобы потом без кастинга.
Используется массив таких структур. Для получения значения по ключу, его пробегаем в цикле. Вроде, все хорошо работает.
Но "по совету" друзей, а также вдохновленный чтением вашей книжки, решил попробовать затолкать эти строки в такой тип данных, как "ассоциативный массив" (язык D, стандартная библиотека Phobos). Вроде, действительно, как раз, подходит! И никакого пробега в цикле не требуется, значение по ключу получается "напрямую". Так дело в том, что после загона в этот самый "ассоциативный массив" (скорость там сложно определить и сравнить, данных, наверно, мало), памяти стало жраться процентов на 30-40 больше. Один и тот же файл. Всё, что делается - загоняется в сишную структуру (первый случай) и в ассоциативный массив (второй). Далее, просто останавливаемся в цикле for(;;), ожидая ввода ключа для получения значения. Вроде, ничего противозаконного не делал. Откуда такие потери в памяти? Или эти "ассоциативные массивы" в принципе должны жрать больше? Представляется, что их реализация хорошая. Так и неужели дело именно в том, что сама реализация таких типов данных просто подразумевает большее потребление памяти?
Ну а вы чего ожидали
Проблемно-ориентированная структура данных, сделанная под данную конкретную задачу, разумеется, будет эффективнее, чем какая-то там "универсальная" — если только её правильно спроектировать. Кстати, не факт, что вы спроектировали правильно: линейный поиск — штука дичайше медленная. То есть память-то вы экономите, а в скорости поиска теряете. Вы вот попробуйте заставить вашу программу сделать не один поиск по ключу, а миллион таких поисков, сразу поймёте разницу.
Впрочем, я считаю, что это не повод использовать готовые библиотеки для структур данных. Надо просто научиться самостоятельно создавать структуры данных с быстрым поиском. Скорость поиска должна быть пропорциональна не количеству элементов, а хотя бы логарифму количества элементов, а в некоторых случаях вообще должна представлять собой константу (если сделать хэш-таблицу), то есть работать с одинаковой скоростью хоть на сотне элементов, хоть на миллиарде.
И ещё, я не уверен, чтО вы подразумевали под "останавливаемся в for(;;)" — вы ведь не имели в виду активное ожидание, правда?
Возможно, делаю
Возможно, делаю чего-то не так. Я не волшебник, только учусь.
Но до собственно поиска дело еще не доходит. Просто читаю файл, каждая строка бьется на две части по csv-разделителю. Обе части забиваю в структуру. Из всех строк получается массив таких структур. Сразу останавливаемся - аналог scanf("%s", any_ID). Ждем ввода. Все. Смотрим память.
Тоже самое при забивании в ассоциативный массив.
Так во втором случае памяти отжирается больше на 30-40%. Вот и не понимаю пока, за счет чего...
scanf("%s", ...) не
scanf("%s", ...) не делайте НИКОГДА, так же как и пресловутый gets. За это с работы увольняют, сам видел. Причины -- жирным шрифтом на стр. 279 (про scanf), потом подробное обсуждение gets на стр. 291-292.
А памяти на 40% больше отжирается за счёт вспомогательных данных, обеспечивающих более быстрый поиск. Если вручную сделать хэш-таблицу, будет тоже примерно такой оверхед.
Да, понял,
Да, понял, спасибо.
По поводу gets() так уже сколько лет gcc верещит, что "НИЗ-ЗЯ!". scanf(), конечно, из той же оперы... А вот библиотеку с хэшами надо бы какую-нибудь отыскать поприличнее, это да! Все написано до нас - надо только найти. Действительно, вполне могут попасться задачи, где удобнее постоянное время доступа к произвольному элементу, несмотря на потерю памяти. Линейным поиск по-идее быть не должен. Разумеется, "сишную" библиотеку. Сравнение того, что у меня получилось на D и на C, шансов для дальнейшего практического использования D просто не дает. Уж больной тормозной! Прототипы программ, если только... Ну, или чтобы посмотреть "как оно там" и как его реализовать на Си. Или, при случае, есть чего сравнить с "плюсами". Мысли-то и там, и там хорошие есть. Вот только за счет скорости исполнения почему-то!) Такое примерно применение...
А вот что касается моей конкретной задачи (примерно полмиллиона строк в память), то там, пожалуй, линейный поиск вполне уместен. Дело в том, что за несколько лет выяснилось, что обращение к разным данным чрезвычайно неравномерно. От очень частых до чуть более, чем редких. Поэтому и был создан массив структур (около двадцать тысяч из полумиллиона) с линейным поиском, а если запрашиваемые данные были из числа "чуть более, чем редких", шло обращение к файлу. Причем, из таких редких запросов создавался дополнительный короткий кэш-файл, к которому впоследствии и обращались в первую очередь. Именно таким образом и покрывалось около 99 процентов запросов. А использование специфической библиотеки привело бы к потери памяти. Вряд ли нужной... Хотя, конечно, все надо бы считать, "на глаз" тут сложно. Но как говоривал в былые времена один легендарно-неуважаемый программист - "Шестьсот сорок килобайт должно хватать для всех!" ;) Тут он прав.
В общем, книжку читал, что-то новое узнал по любому. По мере чтения вопросы возникают, которых могло бы без нее и не возникнуть.
Не откладывая в
Не откладывая в долгий ящик, покуда есть время, по первому же запросу "hash iibrary for C" выгуглил ссылку https://github.com/troydhanson/uthash. Поверху была, так что судьба ее такая. Завелось практически сходу, как под меня написано. Это даже не либа для хэшей, а просто хедер, в котором аж на 1000 строк чего-то умное намакросячено - еще не смотрел (это для тех, кто говорит, что препроцессор Си "устарел безнадежно"). После подключения этого заголовка имеем обычные, как и во всех скриптовых языках хэши. Памяти, как уже и ожидалось жрётся больше, чем при организации поиске в цикле, но скорость, конечно, повыше. Впрочем, на Си это заметно не сильно (и так быстро!). То есть, можно легко уже и рабочий вариант переписать (там за несколько лет памяти-то на машинах прибавилось). Не думаю, что это самый эффективный вариант реализации хэшей, но он очень простой и начальные представления дает всяко.
Что касается применения для этой же цели языка D, то пришлось отказаться от этой затеи. Исходно подкупило то, что "D - язык для системного программирования", "на нем можно писать как на С" и т.п. Писать-то можно, конечно, как на С, только скорость исполнения получается как на D, а то и как на E! :-) Да и если глянуть его историю - D1, более раннюю библиотеку Tango, то там почему-то даже синтаксически попахивает больше Java, чем С.
В общем, полезная у Вас книжка. Даже уже потому, что заставила разобраться и ответить на какие-то вопросы, которые в ней даже не затрагивались.
Вопрос по
Вопрос по первому тому (Паскаль, hello.pas) стр. 201:
"Просто компилятор вынужден загнать в файл сразу всё, что нужно для выполнения практически любых операций ввода-вывода, а мы эти возможности пока не используем".
Получилось у меня даже больше 120 Кб. Нельзя ли "загнать" в файл только то, что нужно? А то такие размеры не радуют совсем. То есть уменьшить вес программы именно на Паскале, не забираясь ни в какие ассемблеры?
Никак
Для FreePascal это минимальный рантайм, то есть такой набор процедур, без которых оно не работает. Можно ли сделать как-то иначе? Можно, но вам для этого понадобится сделать свой собственный компилятор Паскаля.
Ясно, спасибо.
Ясно, спасибо. Просто думал, что поскольку компилятор fpc не самый новый, то и имеется возможность использовать рантайм поменьше, по аналогии с Си, где элементарные программы можно собирать с либами из комплекта VS6 или пользовать TCC (книжка читается под Windows), что делает их размер более приличным.
Точка зрения относительно подсветки синтаксиса нестандартна довольно. Думаю, немногие согласятся. На новогоднюю елку редактор конечно походить не должен, но syntax off - уж очень радикально. Того редактора не видел, но само отсутствие возможности подсветки синтаксиса делают его фактически непригодным для большинства пользователей.
Во-первых, в vim,
Во-первых, в vim, разумеется, есть подсветка синтаксиса — её даже не очень просто отключить (synt off отключает только для текущего буфера, при открытии на редактирование следующего файла снова определяется его тип и включается подсветка), но мне пришлось научиться (найти команду filetype off), поскольку никакие даже зачатки подсветки я на дух не выношу.
Во-вторых, если вы пытаетесь учиться под Windows, я вам, как автор обсуждаемой книги, настоятельно рекомендую на эту книгу больше времени не тратить. Найдите себе другой учебник. Моя книга заведомо бесполезна для людей, не готовых к расставанию с виндой.
Никак
Никак лазутчика изловили, Андрей Викторович? И ведь повезло ещё, что сам сознался! :) Иначе...
Действительно, и как таких только в Интернет пускают!
Задумал как-то виндузятник
Наук постичь хотя б основы.
Тут надо было б аккуратней,
А он попал на Столярова...
Куда ж ты лезешь, бедолага,
Коль даже с Vim'ом не в ладу?
И сделать не успеешь шага,
Считай, накликал уж беду!
И точно - послан уж по маме,
Спасибо хоть, что не в гробу!
Но сразу отлучён от Знаний,
И враз - к позорому столбу!
Это ещё он не лютует,
Это шутейный незачёт!
Убунту хоть поставь какую,
Тогда, быть может, повезёт... ;)
Гыгыгы
Стих понравился :-)
Если серьёзно, ну есть же предисловия, там ясно и недвусмысленно объясняется, почему нужен *nix и почему винда не годится. Но вот нет, поди ж ты. Не, ну ладно человек жаждет остаться под виндой (таких много), но почему тогда именно мои книжки, которые для этого ну никак вообще?
Актуально?
Я так понимаю, что Ваша книга "Программирование на языке ассемблера NASM для ОС Unix" (2010) уже более не актуальна и всю ее информацию с дополнениями можно найти в этом -втором томе?
Совершенно верно
Б'ольшая часть текста книги 2010 года вошла во второй том в виде Части 3; небольшие фрагменты из "Введения" ушли в первый том, во вводную часть (Часть 1). Так или иначе, текст подвергся исправлениям, дополнениям и прочим улучшениям, так что, конечно, правильнее читать новую книгу.
Касаемо Джава
Наверно, за Джавой я бы всё-таки что-нибудь, да оставил. Хотя бы мобилки...
Впрочем, здесь, думаю, контекст, всё же, иной. Речь-то, о самых началах, об обучении. Джава - это больше для пенсионеров. И уж учиться на ней нельзя точно! И потом, "Жаба - отстой!" - это не только лозунг, шутка юмора, это и образ жизни, и подход к делу. Выше совершенно справедливо замечено, что без того "пониженного уровня", который обеспечивают оба тома, в программировании делать просто нечего.
Видел как первый том, так и второй. Не могу сказать, что встретил много незнакомых слов, но читалось с удовольствием. Увлекательно написано! :-) А кое-что из "начал Паскаля" даже было просто незнакомо. Кроме того, в процессе чтения наблюдалось некоторое устаканивающее в мозгах действие. Кстати, жаль, что это произошло не раньше ...
Очень даже похоже на истину, что начинать обучение всё-таки с Паскаля предпочтительней. Я, помнится, начинал сходу с Си. И средний уровень достигнут был довольно давно. Но путь мой был (с сегодняшней уже точки зрения) весьма кривотернист. Слишком много лишнего текста пришлось отработать. Явно можно было обойтись меньшими затратами и временем. Даже жаль, что уже не студент. Как бы то ни было, хорошая учебная литература не может не радовать.
Картинка, кстати, на обложке второго тома тоже ничего себе. Хорошая. Как бы сказали на Украине (вот нравится мне их язык! Несмотря ни на что! :-)) - "навіває"...
Планета Си. #include хорошая погода.
Здесь Плакающий_Туч с Крылатой_Рыбой
Хоронят Жабу - умерла в конвульсиях байт-кода.
И Жук_с_Граблями закопал... Друзья, спасибо!
Про джаву
Если очень нравится JVM (пусть даже и на мобилках), то возьмите Clojure и на нём пишите. Ибо ежели вам сборщик мусора не мешает, то нафига вам тогда таскать за собой прочие рога и копыта от фоннеймановской машины -- присваивания всякие, циклы и прочие исходно противоестественные штучки императивного программирования, не имеющие никаких оснований для существования, если только отойти от фоннеймановской архитектуры.
А в остальном -- спасибо за отзыв, особенно за стих :-) Порадовался.
За Джаву, как
За Джаву, как раз, я особо-то, и не ратовал. Скорее, даже скромно полагал, что Самый Главный Нелюбитель Джавы - это я! :) Вижу, придётся подвинуться... Правда, нелюбовь эта имеет не столь теоретические резоны, тут всё проще - у меня на сегодняшний момент весьма простенький двухъядерник с четырьмя гигами на борту (разумеется, система 32-разрядная). И я бы сказал, хватает с серьёзным запасом. И как-то "модернизировать" комп я не собираюсь. С учётом того, что "помедленнее" как-то "не нравится", очевидно, что Джава мне ни к чему. Ну, а ставить на машину какие-либо системы и программы-студии, которые в принципе предназначены для пожирания жёстких и мягких мегабайтов и многоядерных тактов, мне и в голову никогда не придёт.
Хотелось бы момент ещё отметить, который кажется существенным для общей оценки написанных книг. Скажем так - коммент "из курилки". Там вполне мог бы и остаться. Не думаю, что это правильно...
Один из "незатягивающихся" (на стенке надпись "НЕ КУРИТЬ") нашёл стиль изложения двухтомника схожим со стилем изложения одного из своих преподов (а не студент он уже куда более, чем я!). Специальность у него непрограммерская - психология. Два семестра на основы программирования - за глаза. Пренепременнейше - TurboPascal. Так у них дама, которая должна была читать сей предмет (и даже начала), внезапно заболела декретом, не успев даже поведать о всех тонкостях копирования и вставки текста в этой знаменитой среде (а клятвенно обещала!), и им на весь срок нашли преподавателя "с производства". Он их тут же переориентировал на Си. Что ж, экзамены-то, ему принимать! Так со слов тогдашнего студента, человек этот "с производства", несмотря на то, что мог "как преподать, так и приподдать", был в состоянии объяснить довольно непростые для новичков материи - те же указатели - буквально на пальцах. Процессоры и компиляторы тогда были несколько другие, в частности скорость исполнения передних и задних инкрементов-декрементов отличалась, так он умудрялся объяснять даже трёхэтажные конструкции из указателей, и почему они работают быстрее, чем аналоги с индексной нотацией, языком вполне человеческим. Сейчас, возможно, это уже и не так. Это времена, думаю, скорее, такого компилятора как Watcom (не open!). В общем, оценка деятельности этого временного преподавателя была достаточна высока. И сравнение со ним явно не в минус. Достаточно, возможно, сказать, что этот нынешний психолог, по большей части занимающийся тестированием людей в самых разных целях, знает несколько десятков языков программирования. Но использует он их в целях абсолютно непрограммерских! С его точки зрения, языки программирования являются вполне достаточным средством для выяснения наклонностей человека, его возможностей, способностей, поведенческих стереотипов и способа мышления. Характер же и темперамент легко определяется тем языком, на котором человек пишет предпочтительно (поставленная задача здесь в расчёт не берётся). И вообще, по России, наряду с обязательным изучением русского и родного языка, необходимо включить изучение (с самого раннего детства!) языка Perl (это, кто не знает, самый-самый главный язык!;)), после чего, проведение даже самых простейших из его Perl-тестов (куда там IQ вшивому!), тут же всё покажет об испытуемом - сразу его в дурку отправлять, или же он к чему-то ещё всё-таки пригоден. Надо заметить, что Perl действительно позволяет чрезвычайно разнообразные синтаксические конструкции для выражения одной и той же мысли. И вот они как-то в его непрограммерском совершенно мозгу ассоциируются с какими-то "возможными комплексами", "замещёнными стимулами" и прочими не имеющими никакого отношения к программингу, понятиями. Короче, сплошной "волюнтаризм"!..
Тут подошёл кто-то из административного легиона и указал пальцем на надпись "НЕ КУРИТЬ" (а наказывают там на курение в неположенных местах даже строже, чем за использование откровенно нецензурного слова "Джава" на этом сайте), и поэтому идея обязательного перлообразования всенародной поддержки получить не успела.
Как бы то ни было, очевидно, что если откровенный непрограммист находит для себя понятным и полезным способ изложения тем, явно выходящих за пределы его профессиональной ориентации, то уж людям, изучающим программирование в целях именно профильных, всё это должно быть ясно просто как день. Но, увы, уже который раз попадаются молодые хлопцы, получившие именно профильное образование, но просто не понимающие ЧТО они делают! А хлопцы по всему - не тупые совсем! И такое впечатление складывается, что вина не их вовсе - просто в институте кто-то когда-то не смог сделать то, что должен был - подготовить нормального грамотного специалиста в своей области. А по каким причинам не смог - неважно. Дело не сделано.
А вот здесь картина как раз другая - похоже, наблюдается соответствие. И на сегодня, увы, нечастое...
Ого
Не раскрыть такой коммент я просто не мог :-)
Одного не понял, чем и кому может нравиться Perl. Исторически это первый язык программирования, который когда-то давно в меня не полез :-) При том что я на тот момент уже свыше десятка языков знал, притом очень разных, в их число входили и Лисп, и Пролог, и совсем экзотический Рефал, и всякие C/C++, а Perl мне был нужен здесь и сейчас, я работал в интернет-провайдере, там юзвери развлекались с CGI-скриптами на нём, любимом (1997 год, PHP ещё толком не было), так вот поди ж ты, раз пять книжку О'Рейли про Perl открывал, столько же раз и закрывал, не забравшись дальше десятка страниц. Вот тошнит меня от него, и всё тут. То есть понятно, что имеющийся скриптик я и прочитать могу, и слегка переиначить, но вот чтобы прямо на нём писать — не, не могу.
Думаю, что Perl -
Думаю, что Perl - "потому что Perl"! :)
Ну, или потому, что не так уж много языков позволяют столь разнообразно синтаксически оформлять код - можно хоть перманентной колбасой писать! Попробуйте такое учудить в Python'е - да Гвидо даже и разговаривать с вами не будет, жёсткие отлупы извольте в коде делать, иначе его математическое сознание просто не выдержит такой пытки. Средневековье какое-то... Фортран до такого не доходил. У Ларри всё иначе - не случайно лингвист по образованию. Готов поддерживать во имя сходства с естественными языками чуть не междометия. Два разных человека. Ну, а этот - так и вообще - не программер! Не стоит с ним (даже если спросит!) откровенничать по вопросу какой способ расстановки фигурных скобок вы предпочитаете - тут же последует какой-то диагноз! Не всё так просто... А уж непонимание такой элементарной вещи как "кому может понравиться Perl"... тут уже диагноз может быть и фатальным! :D
Здесь дело не в том, что "Perl"... А в том, что человек-непрограммер менее чем за два семестра успел получить знания по самым основам бытия: "переменная - это указатель", "значение переменной - разыменованный указатель", собственно "сам указатель - адрес (возможно, логический) первого байта стандартного типа или данных (возможно, выровненных) структуры произвольной сложности". То есть, в вопросе о первичности адреса и данных, приоритет за адресом. И уложились эти основы у него фундаментально. А логически объединённые адреса данных и имена функций (адреса их кода!) не что иное, как класс! Чисто полицейская же языковая функция "сокрытия данных" - это просто защита себя любимого от себя же дурака. Для очень многих проектов это абсолютно ни к чему. Дело меняется лишь с усложнением проектов. Но там (это в первую очередь касается кудрявых и румяных молодых ребят от Java, собирающихся вместе радостно спеть и сплясать о не менее кудрявых "перспективах" означенной "Java"), программист уже программистом-то, и не является! Это лишь шестерёнка, пишущая технически необходимую (ПОКА!) часть кода. Программист в больших проектах лишь один. Это тот, кто организует работу этих всех кудрявых шестерёнок. А кудрявые шестерёнки деквалифицируются (если есть от чего!) написанием тучи обязательных геттеров-сеттеров. И такая вот "геттерно-сеттерная" квалификация на сегодня пока ещё нужна. Хотя, какой-нибудь NetBeans, отобедав с аппетитом полутора-двумя гигами памяти, уже умеет их ваять по мановению пары клавиш... Что далее? А ничего! Далее, как и во всех языках, скорее всего, в Java будет появляться всё больше и больше функциональных возможностей. Тенденция, вроде, очевидная. И, как следствие, написание технически нужных на сегодня кусков кода программными средствами. Тем же NetBeans. Вот и все "кудрявые перспективы" современного Java-программиста. Плюс, не надо забывать, что любой язык (за исключением самых простых - ну, Си, конечно же!), имеет свой жизненный цикл. Так что, и кудрявым и пока ещё румяным тоже забывать не следует - "чтобы стоять, я должен держаться корней" никто не отменял...
Отвлёкся малость... Ну, в общем, Perl в данном случае можно рассматривать отчасти и как чудачество. Но лишь отчасти...
Кстати, среди программистов такого тоже навалом.
Канадец Rob Pike, текстовый редактор Acme, про Sam уж вспоминать не будем, дело совсем уж прошлое. Несмотря на весьма положительные и даже восторженные отзывы (в том числе известных весьма персон), никогда не мог понять их смысл! Возможно, каждому своё... Но почему в современном текстовом редакторе нет, чёрт возьми, банальной подсветки синтаксиса?! Может, Пайк позабыл? Ан нет! "Пишите код, ребята, не портите себе глаза!". Вот вам и Пайк. По мне - так "чудачество" - это самый минимум в приговоре!
Голландец Bram Moolenaar, язык программирования Zimbu.
"Дяденька, а почему в вашем языке, в структурных блоках отсутствует открывающая фигурная скобка? Только закрывающая?" - "А на кой? И так же видно, где блок начинается!"... Далее следует просто убийственное математически-строгое обоснование - "Кроме того, сразу отпадает вопрос о \"правильности\" размещения фигурных скобок!". Каково?! Просто Брам-миротворец всех разноскобочных холиваров! :)
И не звали бы их обоих именно так как их и зовут, можно было бы сказать - придурки придурками!
Вот и встречай после этого по одёжке... Так что, по-разному бывает.
М-да
Пожалуй, этот поток сознания я в основном оставлю без комментариев, за одним исключением: таки да, я не использую подсветку синтаксиса. При настройке vim у меня первое действие — в .vimrc добавить syntax off и filetype off. Когда у меня серые буквы на чёрном фоне, я проработать могу часов пятнадцать, не разгибаясь, а вот если буковки становятся разноцветными, да ещё разными по яркости — пиши пропало, уже часа через полтора хочется глаза закрыть и больше не открывать. Вообще не понимаю, как этим можно пользоваться.
Ну и про Perl: всё, что вы тут позиционируете как его достоинства, на самом деле является его недостатками, притом фатальными.
Подсветка синтаксиса
Сурово, ничего не скажешь! :) Хотя и очень субъективно.
Использую Vim с начала века, хоть последние лет 5-6 исключительно в графической ипостаси. Мог бы согласиться, что как встроенные, так и практически все цветовые схемы, встречавшиеся в сети - ужас просто необычайный, но совсем без подсветки я все же не обхожусь. По привычке (как раз начала века) в любой .vimrc вбивается:
autocmd BufEnter *.c colorscheme visualstudio или vcbc, слегка отредактированные.
В память о шестой студии и борланд 5.02.
Только Столярову не говорите! :D
Видимо,
Видимо, всё-таки глаза у всех по-разному устроены. Я не могу работать с фоном, отличным от чёрного, давит на глаза. А на чёрном фоне подсветку как ни настраивай, хоть так, хоть эдак -- любая цветная буковка будет как фонарём в глаз.
Да, конечно, у
Да, конечно, у всех все по разному!
У меня среди знакомых так вообще есть персонаж, который использует исключительно монохромный режим монитора (полстола у него занимает) черт знает каких годов. И лишь там чувствует себя комфортно (с различением цветов у него нормально). Я к таковым не отношусь.
Что же касается редактора Acme, то его просто нельзя рассматривать вне контекста OS Plan9 - он там просто один из основных инструментов. Будучи поставлен на Linux или Windows, он представляет собой нечто совершенно другое, неизвестно для чего нужное. Правда, попадался на Хабре какой-то любитель Acme под Linux, но что-то поверил я ему не сильно. Под Linux есть один редактор. Называется "Vim". Те же, у кого с его освоением возникли проблемы, корячат пальцы на Emacs. У всех же прочих просто рука дрогнула при выборе системы в меню GRUB - заблудились слегка, у них там всяко где-то пониже должно быть написано "Windows".
А под Plan9 (элементарно устанавливается под VBox), штука просто необходимая. Чисто даже из соображений "повышения образованности" поставить можно вполне, да глянуть. Unix'ы разные бывают. Тут такой своеобразный unix, в котором немалую роль играет трехкнопочная мышь. Ну, а авторы, действительно, в представлении не нуждаются! Может, в чем-то и чудаковаты, не без этого...
Точно. Так оно и
Точно. Так оно и получилось - непреодолимые трудности копипасты :)
Зачем вам копипаста?
Выше на этой странице имеется ссылка на архив примеров, в котором имеется в том числе и обсуждаемая программа :)
Первый том не
Первый том не видел еще, а вот второй посоветовали, так и почитал. Действительно, очень удачно у Вас получилось как с собственно объяснением материала, так и с выбором темы, по которой столь компактно и доступно написано очень немногое. Фактически, это необходимый промежуточный уровень между программистом-питекантропом, каковыми, безусловно являются студенты младших курсов и программистом-кроманьонцем (старшие студенты). Пропустив этот неандертальский уровень, подойдя вплотную к С++, люди рискуют познакомиться лишь с самым одельфированным и овасяченным его подмножеством, что приведет их неминуемо к Java, а то и к такому субпродукту как .NET. Нужность самой Java сомнений не вызывает - действительно, бывает и объективная сложность ряда задач, и высокая надёжность требуется при низких требованиях к быстродействию. Однако, во многих случаях применение Java (сейчас ведь ее суют куда ни попадя), явно необосновано. И связано такое применение лишь с отсутствием должной квалификации программиста.
Буквально на днях слушая за пельменями в фоновом режиме русскоязычный скринкаст о началах Java, чуть не подавился от удивления - лектор выразил мысль, что нонича машина Java настолько крута, что программы для нее написанные, работают "чу-у-уточку" медленнее, чем аналог на С++, а в ряде случае (наверно, у него писюк с Java-процессором) еще и быстрее. И хотелось бы верить, что он высказал такую ахинею чисто в пылу лекторском, а ведь могло бы быть и иначе - сам написал на Java, потом на С++, сравнил и убедился, что "на Java быстрее"! Но по любому очевидно, этот самый "промежуточный уровень" у него остался непознанным. Ну, или он изобрел на досуге какой-нибудь особо хитрый замедляющий компилятор для языка С++... Или умудрился как-то скомпилить откровенно плюсовой исходник в байт-код Java, тут уже только гадать можно!
Лишнего опять же в Вашей книге нет, что совсем неплохо. Помнится, TASMы и MASMы под DOS радовали меня не сильно. Хотя, и разумеется, что-то дали...
В общем, нужная книжка, и написана хорошо. Остается надеяться, что она попадет в правильные руки, и количество грамотных, подготовленных студентов подрастет. Спасибо.
Вот уж что-то, а
Вот уж что-то, а джава точно не нужна :) В остальном спасибо за отзыв.
Учебная
Учебная программа из раздела обучения ассемблеру (ubuntu 16.04.1) выдаёт такое:
user1@cmp:~/asm-test$ nasm -f elf asm-test.asm
asm-test.a:11: warning: label alone on a line without a colon might be in error
user1@cmp:~/asm-test$ ld -m elf_i386 asm-test.o -o asm-test
user1@cmp:~/asm-test$ ./asm-test
Hello
Hello
Hello
Hello
Hello
Ошибка сегментирования (сделан дамп памяти)
Подозреваю, что
Подозреваю, что вы допустили опечатку в слове FINISH (например, не все буквы на верхнем регистре, ну или любая другая опечатка).
errata file
Андрей Викторович,
хочу предложить вам полезную вещъ:
заносите найденные ошибки/опечатки в файлы errata_volX.pdf (так они не будут теряться в дебрях комментариев на сайте; так сделано во многих серьёзных книгах, которые out of print или что-нибудь ещё в этом духе касательно переиздания)
Владельцы бумажных версий просто распечатают/вклеют себе эти файлы, а владельцы эл.копий просто будут держать их открытыми в соседней вкладке
Пожалуй,
Пожалуй, хорошая идея -- постараюсь реализовать. Спасибо.
Ошибка
На странице 218 описывается функция case_up.
Помоему, там ошибка в возврате символа в верхнем регистре.
return c-('a'-'Z')
Должно быть
return c-('a'-'А')
либо (что тоже самое)
return 'A'+(c-'a')
Ага, спасибо
Да, вы совершенно правы, очень досадная опечатка. Спасибо за её выявление. Только это стр. 214, а не 218 :-)
const в gcc и clang
Читаю страницу
265
"естественно, компилятор не станет размещать..." строковый литерал "в неизменяемой памяти (кстати, даже в том случае, если массив будет объявлен со словом const)...".
Что-то не так делаю, или не "каждый компилятор"? Похоже, что clang размещает строковый литерал как раз в неизменяемой памяти, хотя при компиляции никаких предупреждений не выдаёт...
Что-то здесь не так
Действительно, обычно компиляторы размещают строковые литералы в неизменяемой памяти, об этом, собственно говоря, как раз и идёт речь на стр. 264-265. Цитируемый вами фрагмент относится к (особому) случаю, когда строковый литерал используется не сам по себе, а в качестве инициализатора для обычного символьного массива. Такой массив, каков бы ни был его инициализатор, размещён будет либо в стеке (если он локальный), либо в сегменте данных.
Если это объяснение не устранило ваши сомнения, давайте сюда ваш пример :-)
Пример
Ага, ну, картинка, видимо, не проходит...
Вот и пример:
И gcc и clang, оба компилируют без вопросов энд предупреждений, однако, при исполнении, в первом случае имеем вывод "Mello", во втором же получаем Segmentation fault. Так и кто же из них прав? :)
Как обычно, правы оба
Программа, как мы понимаем, не вполне корректна, поскольку при преобразовании типов снимает const. Сделать так, чтобы некорректная программа всё же работала, компилятор не обязан, но право имеет. Вот gcc этим правом воспользовался, а clang предпочёл оптимизировать (кстати, довольно чувствительно, ведь отсутствие такой оптимизации в данном случае означает, что при входе в функцию каждый раз строка будет копироваться).
В ваших предыдущих объяснениях я упустил тот момент, что массив вы объявляете с const'ом.
А в книге я написал не совсем верно, это я согласен.
О любви
Да, спасибо... Язык Си всё-таки любит нас. Хотя бы в случае gcc!
Ведь если нет явного приведения к неконстантности, он выдаёт предупреждение. А вот если имеется явное приведение
то он послушно исполняет то, что ему сказали, несмотря на не менее явную подозрительность такого приведения. Но "случайно" никто ничего и не приводит. Язык такой по сути - всё на совести программиста. И просто грех его за это не любить навстречу!
Покрутил - и правда! ;)
Ответственность программиста за всё-всё-всё в языке Си - факт очевидный, разумеется. И доброта "сишных" компиляторов также общеизвестна. Но в нескольких элементарных строках просто прекрасная программная иллюстрация этой "общеизвестности" получилась! Сложно пройти мимо...
Я - GCC. Для вас, ребята,
Любой исходник скомпилю!
Я - очень добрый компилятор,
И "сишников" я ВСЕХ люблю!
Коль библию от Кернигана с Ритчи
Освоил ты недрогнувшей рукой,
То ты мне очень даже симпатичен,
И я на всё готов пойти с тобой!
Я разрешу всё-всё, что ты умеешь,
И то, что понимаю я...
Лишь если только ОЧЕНЬ обнаглеешь,
Слегка предупрежу тебя!
Но если ты не внял совету,
Считаешь, что ты круче GCC...
Теперь лишь на себя ты сетуй,
И бейся лбом об стенку со всех сил! ;)
Строго говоря,
Строго говоря, компилятор не добрый, и не злой. Он просто - компилятор.
Он должен из синтаксически верного исходного кода получить объектный. Вот и всё!
А разбирать ваши каракули, залезать вам в мозги... Не царское это дело. Для подобных целей существуют программы класса lint. Если какие-нибудь Убунты, то splint, скорее всего, уже установлен. Вот он, как раз, в красках покажет всю глубину вашего нравственного падения, тут никакие "-Wall" даже рядом не лежали.
А то, понимаешь, "плохой, хороший, злой"...
Компилятор должен работать быстро. И не надо на него вешать несвойственные ему функции.
Опечатка
На странице 229 говорится, что если в программе встречается присваивание x=y, то на выходе сгенерируется что-то вроде
mov eax, [x]
mov [y], eax
Насколько я понимаю, это присваивание y=x; x=y выглядит так:
mov eax, [y]
mov [x], eax
Вы абсолютно правы
Очередная досадная ошибочка. В своём экземпляре я её пометил, так что если дойдёт дело до переиздания (что, к сожалению, вряд ли), то в следующей версии этой ошибки не будет.
Спасибо!
По поводу
По поводу содержания будущей книги по C++:
1. Сделайте, пожалуйста, себе пометку, что в томе по C++ надо бы объяснить что такое lvalue и rvalue. В вашей книге "Введение в язык C++" этого нет, да и в русскоязычной литературе практически не освещено.
2. Еще в книге по C++ надо бы добавить серьезный раздел о том, как читать и распарсивать C++ код. Объяснить почему * или & прилипает то к типу, то к имени, и есть ли между такими написаниями разница. Рассказать про вывод типа "по спирали". Дать всякие мнемотические правила, которые помогут понимать код программистов, любящих обфусцировать плюсовый код и использовать краевые эффекты.
3. Рассказать про терминологию "на стеке", "в куче". Таких понятий практически нет в русскоязычных книгах по плюсам. Объясните, в какие моменты где создаются/удаляются данные.
4. Обязательно рассказать про умные указатели, объяснить проблемы обычных указателей. Обязательно объяснить теорию владельцах ресурсов, о разделении ответственности за ресурс, подробно, как вы умеете. Рассказать про разные виды умных указателей, в каких случаях какими правильнее пользоваться.
Без этих тем, считаю, книга не будет полной.
Часть этого уже есть
Прочтению сложных типов в только что вышедшем томе посвящён параграф 4.13.3 (стр.401), а правило одного владельца для структур данных рассказано в первом томе, пар. 2.17.4 (стр. 457). Стек и куча тоже многократно упоминаются в уже вышедших томах. Про "прилипание" звёздочки см. комментарий на стр. 250 (во втором томе).
Насчёт умных указателей можно подумать -- если дело дойдёт до обзора "продвинутых приёмов". Я сейчас довольно смутно представляю, как будет выглядеть четвёртый том, если он вообще состоится.
Да, и еще.
Да, и еще. Надеюсь, в вашем эпическом труде найдется немного места для рассказа о DSL. В каких условиях при каких классах задач имеет смысл создавать DSL. Что, почему, зачем, небольшой пример. Инструментарий.
Об этом тоже
Об этом тоже можно подумать, хотя на первый взгляд непонятно, как об этом писать вообще. Чтобы показать, что тот или иной DSL зачем-то нужен, необходимо рассказать его предметную область, а такие области, где нужны DSL, простотой не отличаются.
Про rvalue и lvalue вы
Про rvalue и lvalue вы ничего не сказали. Будут?
Правило одного владельца у вас написано в части, посвященной Паскалю. На самой последней странице книги (стр. 457), в стиле "а на последок я скажу". А нужно именно в раздеде по C или C++, видимо в теме про умные указатели. Может быть в примечаниях про Rust написать, где принцип одного владельца возведен в абсолют.
И наверно, коль это вошло в стандарт, нужен раздел про лямбды/замыкания. Опять же, на русском языке по этому делу вменяемых книг не найдешь.
Про rvalue и lvalue вы
Про rvalue и lvalue вы ничего не сказали. Будут?
В принципе, это надо было в часть по чистому Си включать — но я подумаю на эту тему. В принципе, это вполне нормально вписывается в рассказ о ссылках.
Правило одного владельца у вас написано в части, посвященной Паскалю. На самой последней странице книги (стр. 457), в стиле "а на последок я скажу". А нужно именно в раздеде по C или C++, видимо в теме про умные указатели.
Я пока не уверен даже, что нужна тема про умные указатели как таковая. В одной книге невозможно рассказать всё обо всём.
про Rust написать
Я обычно не пишу о том, чего не знаю. Вероятность того, что я буду знать Rust ко времени работы над четвёртым томом — ну, в общем, не слишком высока.
коль это вошло в стандарт, нужен раздел про лямбды/замыкания.
Ни про какие примочки из C++/11 и C++/14 я не стану писать ни при каких условиях. Для всех этих "стандартов" у меня только одна фраза — их авторов нужно расстрелять как особо опасных международных террористов. Ко всем существующим стандартам чистого Си (да, и к ANSI C) это тоже относится, хотя, быть может, в меньшей степени.
Xто делать с обнаруженными ошибками?
Например, страница 139-140, в описании написано, что цикл должен печатать квадраты от 1 до 25, а в коде от 0 до 25.
Ну а что с ними
Ну а что с ними делать, сообщать о них, видимо. Если когда-нибудь дело дойдёт до переиздания книги, ошибки можно будет исправить.
Только страницы в данном случае 239-240, а не 139-140 :-) И там на 241 странице есть ещё более противная ошибка: написано j++, а должно быть j--.