Собственно технические детали темы неоднократно обсуждались на форуме, но хочу понять не "как исправить", а "зачем вообще задумано"? С точки зрения оптимизации по скорости следует уменьшать количество переходов, а уж двойных (переход на переход) и вовсе избегать, но и Масм и Дельфя упорно вызывают API через call на jmp. Как лечить Масм знаю, но хочу понять что это - просто глупость? или есть хоть какие нибудь плюсы? Попадалось мнение, что это типа экономия одного байта в инструкции call, за счёт добавления ещё шести - сомнительная надо сказать экономия ) Еще Fasm, Дельфя, и многие асм программеры обожают пихать данные в сегмент кода, а потом через них джимпать или калать (если нужно совместить переход с занесением в стек ссылки на данные). Если для Fasm это хотя бы понятно - ну не умеет он раскидывать смешанные в исходнике данные и код по соответствующим сегментам в готовой проге, то зачем так поступают программеры, имеющие возможность выбора? - что тоже есть какие нибудь плюсы?
Y_Mur Мои домыслы. Для переносимости кода. Такой код можно перенести на другой адресс в памяти и он будет работать. Это преднозначено в первую очередь для библеотек, если у них базовый адресса совподут то их можно будет разместить по разным адресам. Про фасм не скажу, а вот про дельфи. Это реализация ООП для вызова методов класса. Также такая вещь как процедурная переменная. Удобно если нужно делать вызов различных процедур по назночению но одинаковых по типу вызова.
Pavia Сомнительная переносимость - несколько джампов и куча "непереносимых" данных. Вообще, говорили, что это для экономии размера кода.
IceStudent Тогда уж не для экономии кода. А для уменьшения exe файла. В таком случаи таблица экспорта будет маленькой по одному адрессу на jmp в замен по адрессу на вызов через call.
Pavia Кому-то нужно повторить структуру PE файла... Y_Mur 6 байт добаляются всего 1 раз, а 1 байт экономится при каждом вызове. Следовательно, если вызовов много, получаем небольшую экономию. Тем не менее, thunk'и придумали не для этого. К примеру, если поубирать thunk'и в либах импорта, ms link.exe перестаёт работать в режиме /OPT:REF. Polink работает нормально.
Опять не понятно: Переносимость кода И jmpы сваленные в кучу в конце программы, и непосредственный вызов API через call используют косвенные переходы, т.е. реальные адреса API всё равно хранятся в таблице в сегменте данных. А как раз call насколько я помню в отличие от jmp не может быть относительным, и потому если в проге все косвенные call заменены на прямые, то при переносе кода их нужно корректировать так, что где выигрыш? Наоборот тут придётся корректировать и адреса call и ссылки на данные в jmp. Или я чего-то недопонимаю? Экономия размеров кода \ exe файла Дабы сэкономить на этом нужно соблюсти условие: "вызовов API более чем в шесть раз больше чем ссылок на внешние функции". Сравните на маленьких файлах (где каждая функция вызывается по одному разу) и убедитесь, что размер exe с call на jmp больше, а не меньше И даже если соблюсти это условие, то получить выигрыш более 3% крайне сложно, неужели из-за этого терять скорость? Имхо на выравнивании exe секций портери больше Pavia Ничего не понял про ООП и процедурные переменные... Какая им разница куда ведёт переданная через стек ссылка к примеру на выводимую на экран текстовую строку - в сегмент кода или в сегмент данных? А проигрыш очевиден - лишний переход...
Quantum Ещё раз заглянул в масм справку и не понял... /OPT:REF это зачем? - ни разу эту фичу не использовал.
Y_Mur Я же написал, что выйгрыш в размере - не основная фишка данного метода. Тут пишут, что thunk'и сокращают кол-во используемых релоков, но это не так. Кстати, в вики тоже самое написано. Странно... Вообще, если учесть, что thunk'и используют недокументированный формат comdat'ов в либах импорта, возможно, у мс когда-то имелись секретные поводы использовать эти самые thunk'и. Данная фича включена по умолчанию. Зачем - см. msdn.
1) Безусловный переход (если память не изменяет) конвеер не срывает. 2) Сами по себе API функции, как правило не очень быстро работают. И вызывать их слишком часто толку нет. И какой спрашивается смысл тут что-то экономить? Ну сделаешь что-то на один такт быстрее. Этого никто не заметит. Да и потом Дельфя и Масм задуманы совсем не для генерации оптимального кода, а для облегчения работы.
На счёт "вызовов API более чем в шесть раз больше чем ссылок на внешние функции", эт я погорячился - нужно добавить ещё 4 байта в таблицу настраиваемых адресов, итого в 10 раз ), т.е. получить выигрыш в размере трудно, а вот проигрыш - запросто )) Quantum Ну положим в масм по умолчанию /OPT:NOREF, но всё равно разберусь - заинтересовал Proteus 1. Как писал Leo, ещё как срывает, и ещё где то он писал, что переход на переход это ещё хуже Вопрос не про копеешную экономию, а про стиль программирования - зачем делать откровенно антииоптимизирующие телодвижения, если ничего не мешает их не делать? Большая глупость складывается из множества маленьких )) Итого: попутно выяснилось удвоение затрат на настройку перемещаемых адресов, а из плюсов пока только версия cpp_and_wasm - проще API перехватывать ))
Только не пинайте сильно Я не такой великий спец по оптимизации как, например, leo или Black Mirror. Поэтому я могу ошибаться Но как и вы я неоднократно задумывался над вопросом почему call на jmp вместо call [...] Мне кажется это связано с тем, что ранние процессоры, не помню точно 286 или 386 не поддерживали косвенные переходы. И эта фича используется для, блин, совместимости. К тому же помниться мне в Delphi 7 до сих пор есть опция коррекции при делении. Это когда Intel выпустила Pentium который делил неправильно в некоторых случаях. К счастью она отключается Видимо, все это связано с политикой больших корпораций Microsoft и Borland. Код должен работать всегда и везде
Y_Mur По умолчанию именно REF. При включенном REF линкер пытается отследить модули, к которым есть символьные обращения через extern, но фактически не используются. Такие модули линкером исключаются в плане оптимизации образа по размеру. Это не касается comdat'ов, которые оптимизируются в любом случае (и в REF и в NOREF). Более подробно написано в msdn. Тем не менее, на практике наблюдаются некоторые противоречия в логике линкера, если поубирать из импорта thunk'и, т.е. те тупые вызовы переходников. Можете запустить линкер в режиме /verbose для наглядности. Вижуал давно перестал их делать. Miller Rabin Ы???
IceStudent Это запись в обьектнике, которая попадает в образ только при явном обращении. Весь импорт строится из comdat'ов. Получается как будто обьектник внутри другого обьектника. comdat'ы встречаются не только в импорте, но и в обычных обьектниках.
После медитации с дебуггером дошло - masm генерит относительные call (эт в #8 у меня просто остались воспоминания о реалмоде, а в протект относительные call есть) - значит в таблицу перемещений попадают только jmpы, которых меньше и за котрыми не нужно гоняться по всему файлу - соответственно кеширование рулит, и время подготовки проги к запуску немного уменьшается. Значит некоторая польза для больших прог интесивно использующих API всё таки есть Quantum Попробовал /OPT:REF, /OPT:NOREF линкер из Masm32 v9 линкует без проблем в обоих вариантаx
Quantum All И теперь остался за кадром только пункт 2 вопроса: Все согласны, что: Код (Text): push MB_OK call a1 db 'Ура заработало ! ', 0 a1: call a2 db 'Всем привет', 0 a2: push 0 call MessageBoxA вместо: Код (Text): push MB_OK push ссылка на строку 'Ура заработало ! ', 0 push ссылка на строку 'Всем привет', 0 push 0 call MessageBoxA не есть хорошо?