Анатомія C Run-Time, або Як зробити програму трохи меншого розміру, Різне, Програмування, статті

Зазвичай C/C++-Програма спирається на потужну підтримку З Run-Time Library – бібліотека часу виконання мови C, далі – CRT; Більш рідкісне назва – RTL (Run-Time Library). Багатьом функцій цієї бібліотеки для правильної роботи потрібна додаткова ініціалізація (CRT startup code). Зокрема, для виведення тексту на консоль за допомогою функції printf необхідно, щоб дескриптор стандартного виводу stdout був попередньо пов’язаний з пристроєм виводу операційної системи (наприклад, стандартним висновком і консоллю Win32). Те ж саме справедливо і для функцій роботи з купою – таких, як malloc для C і оператора new для C++.

Таким чином, навіть у мінімальній програмі, яка містить виклик printf або спробу виділення динамічної пам’яті, буде міститися значний (для такої програми) код ініціалізації CRT – понад 30 кілобайт.


ПРИМІТКА При використанні CRT у вигляді додаткової динамічної бібліотеки (DLL) розмір виконуваного модуля може бути менше 30 Кб – про це мова піде трохи пізніше.


При подальшому розгляді справа виявляється ще гірше. З’ясовується, що ініціалізація CRT потрібна, навіть якщо жодна з вхідних в неї функцій явно в програмі не використовується.


Так, деякі операції з плаваючою точкою вимагають наявності коду ініціалізації: наприклад, на випадок, якщо буде виконуватися обробник виняткових ситуацій (floating point handler). Оголошення глобальної змінної, що є екземпляром класу, що має конструктор або деструктор, теж вимагає наявності стартового коду CRT. Це відбувається через те, що виклики конструкторів і деструкторів в VC реалізовані як частина стартового коду CRT. Використання механізмів обробки виключень C + + і Run-Time Type Information (RTTI) також тягне за собою необхідність ініціалізації.


Виходячи з цього, розробники сучасних компіляторів C + + будують CRT таким чином, щоб її стартовий код включався в програму за замовчуванням. В більшості випадків це – саме ту поведінку, яка вимагається. Справді, великий проект на C + + рідко обходиться без використання CRT-функцій або обчислень c плаваючою крапкою. Та й “доважок” в 30 Кб в такому разі невеликий.


Якщо це вас влаштовує, проблема зі згаданим ATL-проектом вирішується досить просто. Необхідно зайти в налаштування проекту (“Project” – “Settings”), вибрати потрібну Release-конфігурацію і на закладці “C + +” видалити опцію препроцесора _ATL_MIN_CRT. Питання буде знято. Далі можна не читати.


Але зустрічаються випадки, коли вважаєш буквально кожен байт виконуваного модуля. Це може бути ядро ​​інсталятора або саморозпаковується архіву, елемент управління ActiveX, який скачується через Інтернет, або додаток для вбудованої системи. Компілятори C++ (І Visual C + +, в тому числі), на мій погляд, найбільш підходять для такого роду розробок. Додаток може, врешті-решт, складатися з великої кількості модулів, і мало що означають 30 Кб можуть перетворитися в кілька сотень кілобайт, а то і мегабайт. Але для контролю над процесом складання доведеться зануритися в деякі деталі реалізації підтримки CRT.


main або WinMain?


Серед початківців програмістів можна почути таку думку: для консольної програми використовується тільки функція main, А для віконної – WinMain. Це думка, хоч і підтверджене умовчаннями компілятора і лінкера, в загальному випадку, є помилковим.


Щоб трохи розважитись, проведемо експеримент. Створимо файл test.cpp:

#include <windows.h>

int main()
{
MessageBox(0, “Hello from main()”, “A test program”, MB_OK);
return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)
{
MessageBox(0, “Hello from WinMain()”, “A test program”, MB_OK);
return 0;
}


Увага, питання: що з’явиться на екрані після запуску такої програми? Постарайтеся відповісти на це питання, не заглядаючи у подальший опис.


ПРИМІТКА Я не став розглядати ще два можливих варіанти стартовою функції: wmain або wWinMain, Призначених для проектів, компільованих в Unicode. Крім того, при створенні DLL є ще один варіант стартовою функції – DllMain.


Точка входу в програму


Функція [w]main або [w]WinMain, З якої починається виконання програми, зовсім не є точкою входу виконуваного модуля! Насправді, програма на C + + починає роботу з виконання спеціальної процедури ініціалізації. Що стосується Win32, то адресу цієї процедури і міститься в поле AddressOfEntryPoint заголовка Portable Executable (PE) виконуваного файлу. Вона являє собою звичайну функцію C, описану до угоди про виклики __ stdcall. В залежності від налаштувань проекту, в Visual C + + ця функція може називатися [w] mainCRTStartup, [w] WinMainCRTStartup або _DllMainCRTStartup (символ “w” додається до імені для Unicode-проектів). Конкретно ж для складання програми ім’я функції-точки входу можна задати опцією лінкера /entry. Умовчанням для Visual C + + є “mainCRTStartup”. Все сказане справедливо і для деяких інших компіляторів C + + для Win32.


Що ж відбувається під час її виконання? Ось типовий сценарій роботи такої функції (випадок DLL тут не розглядається).



Тепер, нарешті, можна відповісти на моє запитання: він був заданий некоректно :). Справді, результат складання залежатиме від набору опцій компонувальника, встановлених в проекті або за замовчуванням.


Так, наприклад, при виклику компілятора в командному рядку таким чином:


cl test.cpp user32.lib

ми отримаємо консольну програму і повідомлення “Hello from main ()” (згадайте, що говорилося про замовчуванням).


А викликавши компілятор ось так:


cl test.cpp user32.lib /link /entry:WinMainCRTStartup /subsystem:console

ми отримаємо “чудо дивовижне”: програму, у якій виконується функція WinMain, Але створюється вікно консолі.


Код ініціалізації глобальних змінних


Як в VC + + реалізований виклик ланцюжка функцій ініціалізації / завершення?


Наявність в програмі хоча б однієї глобальної змінної – екземпляра класу – змушує компілятор зробити наступне. По-перше, він генерує невидиму за межами модуля функцію, в якій і виконуються необхідні дії – обчислюється значення ініціалізатор або викликається конструктор. Далі створюється спеціальний запис з покажчиком на цю функцію в сегменті з ім’ям виду “. CRT $ xxx”. Детально розбирати формат іменування сегмента ми не будемо, зараз важливо тільки те, що всі сегменти такого типу будуть при складанні об’єднані в алфавітному порядку в один сегмент. Таким чином, в момент старту програми в пам’яті перебуватиме масив покажчиків на функції, при виклику яких і відбудуться необхідні дії. У стартовому коді CRT VC цим займається функція _initterm.


А чому тут використовується термін “функції ініціалізації / завершення” замість термінів “конструктори / деструктори”?


Нагадаю, що стандарт мови C + + дозволяє ініціалізацію змінних за допомогою неконстантних виразів. Якщо змінна (навіть простого типу) описана в глобальній області, то її ініціалізатор повинен бути виконаний до виклику функції main / WinMain:


int len = strlen(“Hello, world!”);

Обробка в цьому випадку нічим не відрізняється від ініціалізації екземпляра класу має конструктор.


Код завершення


Згадавши ініціалізацію CRT, не можна промовчати про код очищення, або завершення. У ньому виконуються дії зворотного характеру (і, в тому числі, деструктори глобальних змінних). Що дійсно заслуговує опису, так це те, що код очищення можна викликати власноруч. Так-так, він міститься в функції exit. Якщо ж не викликати її явно, то вона викличеться після повернення з main / WinMain. Найбільш виразну реалізацію вищесказаного я зустрів одного разу у вихідних файлах CRT компілятора WATCOM C + +:


exit(main(__argv, __argc, __envp));

Тобто, можна сказати, що все виконання програми має на меті одержання параметра для функції exit. 🙂


ПРИМІТКАВзагалі-то, exit (вірніше, можливість її прямого виклику) є, скоріше, “пережитком” з часів програмування на C. При виклику цієї функції з програми на C + + не виконаються деструктори для локальних змінних (що природно, оскільки, на відміну від глобальних об’єктів, їх деструктори ніде не зареєстровані). Крім того, виклик exit з деструктора може привести до входу програми в нескінченний цикл, так що не зловживайте цією функцією.

З часів створення бібліотеки мови C залишилася й така можливість, як реєстрація ланцюжка обробників завершення за допомогою функцій atexit/_onexit. Функції, зареєстровані викликом atexit / _onexit, будуть викликані в ході завершення програми в порядку, зворотному порядку їх реєстрації. Для програми на C + + з цією метою краще скористатися глобальними деструкторами.


Насправді, в програмі на VC реєстрація деструкторів глобальних об’єктів також виконується за допомогою внутрішнього виклику atexit після виклику конструктора. Це має досить вагомі підстави: якщо конструктор об’єкта викликаний не був, то не буде викликаний і його деструктор. Але, в будь-якому випадку, це – деталь реалізації, на яку покладатися не варто.


Усередині exit міститься виклик функції більш низького рівня – _exit. Її виклик не призведе до виклику деструкторів і exit-обробників, а тільки виконає найнеобхіднішу очистку (не буду вдаватися в подробиці, зазначу тільки, що при цьому викликаються C-термінатори (функції з таблиці в сегментах “CRT $ XT [AZ]”), зокрема, підчищають low-level i / o) і завершить програму викликом функції Windows API ExitProcess.


І, нарешті, функція abort є способом “пожежного” завершення програми. Вона виводить діагностичне повідомлення і також викликає _exit для завершення процесу.


Виклик будь-якої з цих функцій призведе до необхідності включення стартового коду CRT.


Зменшуємо розмір виконуваного модуля


Але в нашому прикладі немає нічого, що вимагало б використовувати CRT. Більш того, включивши оптимізацію за розміром (/O1) І генерацію карти виконуваного файлу (Generate Link Map, /Fm), Можна помітити, що розмір функції main – всього 23 байта. А розмір виконуваного модуля становить близько 36 кілобайт. Невже не можна його трохи зменшити?


Звичайно, такі способи існують, і деякі з них я опишу нижче. Але важливо розуміти, що кожен з них не є загальним рішенням (інакше саме він використовувався б за замовчуванням), і має свої недоліки.


Використання зовнішньої бібліотеки CRT


Відкомпілюємо нашу програму наступною командою:


cl /MD test.cpp user32.lib

Розмір отриманого в результаті EXE-файлу складає близько 16 кілобайт. Що за чудеса? Куди поділася половина виконуваного модуля? Невже він “схуд” за рахунок виключення CRT?


І так, і ні. Опція компілятора / MD вказує використовувати для складання бібліотеку MSVCRT.LIB. У ній міститься тільки той набір коду, який дозволяє лінкера дозволити зовнішні зв’язки. А сам код CRT знаходиться у динамічній бібліотеці MSVCRT.DLL в системному каталозі Windows. Ця багатопотокова бібліотека використовується і деякими безкоштовними компіляторами C / C + + для Windows, наприклад, MinGW.


Таке рішення досить зручно, якщо проект складається з декількох модулів – кожен з них стане менше на обсяг рантайм. Крім того, воно дозволяє Microsoft виправляти помилки у вже випущених програмах простою заміною старої DLL на виправлену версію. Цей підхід активно використовується багатьма розробниками, які використовують бібліотеку MFC: якщо в опціях проекту вибрати “Use MFC in a shared DLL”, то доведеться використовувати динамічну версію CRT, інакше проект попросту не збереться. В інтегрованому середовищі версія CRT вибирається у властивостях проекту: на закладці C / C + + в категорії Code Generation.


Погана новина полягає в тому, що MSVCRT.DLL існує не на всіх версіях Windows. Вона почала поставлятися в складі ОС, починаючи з Windows 95 OSR2. Додаток, запущене в системі без цієї бібліотеки, виконуватися не буде. Правда, таких систем стає все менше і менше.


Зменшення вирівнювання файлових секцій


Можливо, власники Visual C + + 5.0 помітили, що у них в результаті виходять EXE-файли куди меншого розміру, ніж сказано тут. Справа в тому, що компонувальник версії 5.0 використовував вирівнювання секцій виконуваного файлу на величину 512 байт. Починаючи ж з версії 6.0, при складанні програми використовується інша величина вирівнювання – 4К. Це дозволяє швидше завантажувати такий файл в Windows 98 і більше нових версіях ОС.


Повернути колишню величину вирівнювання можна, задавши недокументовані опцію компонувальника /opt:nowin98:


cl /MD test.cpp user32.lib /link /opt:nowin98

Розмір EXE в результаті становить менше 3-х кілобайт! Але не забудьте, що такий файл буде повільніше завантажуватися в пам’ять, і що він як і раніше вимагає наявності MSVCRT.DLL.


Радикальні заходи: відмовляємося від CRT Startup


Якщо ампутація здається вам розумною хірургічною операцією, то стартовий код CRT можна викинути з програми зовсім.


Що це означає? Відмовившись від деяких звичних зручностей, які надає CRT, можна писати на C / C + +, не використовуючи можливостей, які потребують підтримки з боку CRT.


Справді, для файлових операцій можна використовувати функції Win API, замість динамічної пам’яті C + + використовувати купу (хіп) Windows, для форматування можна використовувати wsprintf замість sprintf, Для порівняння рядків – lstrcmp замість strcmp і т.д.


При цьому важливо розуміти, що CRT – це звичайна бібліотека, функції якої цілком можна викликати з такої програми (як і з програми на асемблері). Головне – це відмовитися від функцій, які тягнуть за собою включення роздутого коду ініціалізації (або, в крайньому випадку, включити його необхідну частину самостійно).


Метт Пітрек, давній провідний колонки “Under The Hood” в Microsoft Systems Journal (нині – MSDN Magazine), присвятив цьому питанню цикл статей в MSJ під загальною тематикою “Code Liposuction” (“знежирення коду”). Зацікавлені можуть знайти їх в архіві Periodicals MSDN.


Більш свіжа інформація міститься в його статті “Reduce EXE and DLL Size with LIBCTINY.LIB” в січневому випуску MSDN Magazine за 2001 рік. Пропонована автором версія “крихітної” бібліотеки виконання виконує мінімальну ініціалізацію (наприклад, викликає конструктори глобальних об’єктів) та навіть надає власні версії таких функцій, як printf і malloc. При цьому розмір виконуваного модуля виявляється часто менше 3 Кб.


Але не будемо забиратися так далеко – адже в нашому коді немає ніяких конструкторів, правда?


В даному випадку можна просто вказати, що функція main буде точкою входу в програму (замість функції ініціалізації):


cl test.cpp user32.lib /link /entry:main /opt:nowin98 /subsystem:console

В результаті також отримаємо виконуваний файл розміром менше 3 Кб (я знову використав опцію / opt: nowin98). Різниця тепер лише в тому, що він не вимагає зовнішньої CRT-бібліотеки (бібліотека user32.lib необхідна для функції MessageBox, але вона є частиною ядра Windows).


Версія ATL: макрос _ATL_MIN_CRT


Придатність цього підходу доводиться тим, що з його допомогою створено безліч легких COM-компонентів. Але нерозуміння принципів його роботи може легко завести в глухий кут, як видно з цитати на початку статті.


У складі бібліотеки ATL версії 3 і більше ранніх є файл atlimpl.cpp. Він, як правило, включається в один з вихідних файлів проекту (найчастіше в stdafx.cpp) за допомогою директиви # include. В atlimpl.cpp знаходиться “полегшена” реалізація стартового коду CRT: в неї входять тільки варіант функції xxx CRTStartup, згаданої раніше, і “обгортки” для роботи з динамічною пам’яттю – функції malloc, calloc, realloc, free і оператори new / delete. Вони безпосередньо викликають функції Windows для роботи з купою – HeapAlloc і HeapFree. Як не дивно, цього достатньо, щоб змусити запрацювати без CRT startup безліч програм.


Власне, сама ця реалізація доступна, тільки якщо визначений символ препроцесора _ATL_MIN_CRT. Таким чином, є можливість легко управляти включенням або виключенням стартового коду CRT.


ПРИМІТКАВажливий момент при використанні макросу _ATL_MIN_CRT: як і раніше не можна включати оголошення глобальних змінних, класи яких мають конструктори або деструктори, так як код, їх викликає, міститься тільки в CRT. Ця проблема вирішена в бібліотеці ATL 7.0 (не дивуйтеся, як і багато інших додатків Microsoft, ATL перескочила з версії 3 на версію 7), що поставляється з компілятором MS VC + + 7.0. Тим ж, хто користується колишніми версіями компілятора, можу порадити скористатися відмінною бібліотекою Andrew Nosenko”s ATL/AUX Library, В якій міститься код виклику конструкторів / деструкторів. Для цього необхідно включати в проект замість atlimpl.cpp файл AuxCrt.cpp з комплекту бібліотеки.

Хто винен?


Тепер ясно, що причиною появи помилки “unresolved external symbol _main” стало включення стартового коду CRT. Тобто, була явно або неявно використана будь-яка функція, яка містить посилання на структуру даних, що знаходиться в модулі з кодом ініціалізації. При включенні компонувальником в програму цього модуля виникає наступна зовнішня посилання: в тілі mainCRTStartup є виклик main. От і все, ми отримали наше “улюблене” повідомлення про помилку.


Окремою “захоплюючій” стадією збірки додатку є пошук функції або фрагмента коду, що викликав таку ситуацію. Для цього застосовуються такі кроки:



Використання Standard Template Library


А як же щодо Standard Template Library (STL)? Наскільки вона зав’язана на CRT, чи можна використовувати її в надмалих проектах?


Реалізація STL від Dinkumware, Що поставляється разом з VC 5.0 і 6.0, доступна у вихідних файлах, так що проблем з компонуванням не виникає. В крайньому випадку, завжди можна виправити исходники або зробити якусь заглушку на # define “ах (Перебиває імена конструкцій, що тягнуть за собою CRT). Інша проблема – в тому, що STL повсюдно використовує оператори динамічного виділення пам’яті. Як вже говорилося, це викликає необхідність власної реалізації операторів new / delete. Це можна зробити, наприклад, так (ідея запозичена з atlimpl.cpp):


// stub.cpp – the “mini-CRT” implementation file

void* __cdecl malloc(size_t n)
{
void* pv = HeapAlloc(GetProcessHeap(), 0, n);
return pv;
}
void* __cdecl
{
return malloc(n*s);
}
void* __cdecl realloc(void* p, size_t n)
{
if (p == NULL) return malloc(n);
return HeapReAlloc(GetProcessHeap(), 0, p, n);
}
void __cdecl free(void* p)
{
if (p == NULL) return;
HeapFree(GetProcessHeap(), 0, p);
}
void* __cdecl operator new(size_t n)
{
return malloc(n);
}
void __cdecl operator delete(void* p)
{
free(p);
}


Ось приклад програми, яка буде спокійно зібрана за допомогою такого підходу без стартового коду CRT:


#include <windows.h>
#include “stub.cpp”
#include <map>

typedef std::map<int, int> IntMap;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd )
{
IntMap m;
for (int j=0; j<100; j++) m[j*j]=j;
IntMap::iterator i=m.find(49);
MessageBox(0, (i==m.end())?”49 was not found”:”49 was found”,
“std::map test”, MB_OK);
return 0;
}


Для складання цього прикладу необхідно використовувати наступну командний рядок:


cl test.cpp user32.lib kernel32.lib /link /nod /opt:nowin98 /subsystem:windows /entry:WinMain

Бібліотека імпорту kernel32.lib необхідна для функцій роботи з Win32-купою.


Що стосується інших реалізацій STL, надам слово Павлу Блудову:


“Страшна таємниця STL від SGI і HP в тому, що їм абсолютно не потрібна CRT.


З двома застереженнями:



  1. Не використовується C + + Exception Handling

  2. (Випливає з першої) визначено макрос __ THROW_BAD_ALLOC, наприклад, так:

#ifndef _CPPUNWIND 
#define __THROW_BAD_ALLOC
::MessageBox(NULL, _T(“STL: Out of memory.”), NULL, MB_OK / MB_ICONSTOP);
::ExitProcess(-5);
#endif _CPPUNWIND
#include <stl_config.h>

якщо подивитися на __ THROW_BAD_ALLOC, то він являє собою


#define __THROW_BAD_ALLOC fprintf(stderr, “out of memory
“); exit(1)

саме ця строчка, і ніяка інша, потребує CRT. Ну, якщо бути зовсім точним, std :: string “у може знадобитися CRT. Тут уже нічого не вдієш. Використовуйте WTL :: CString.


Павло. ”


Слова про std :: string в повній мірі справедливі і для реалізації STL від Dinkumware. Якщо ви шукаєте реалізацію повноцінного строкового класу, не використовує стартовий код CRT, раджу поглянути на CascString в складі бібліотеки ascLib.


Директива # import та її обмеження в полегшених проектах


Частою причиною появи залежності від CRT є необдумане застосування директиви #import – Розширення Visual C + + для зручності роботи з COM-об’єктами, які надають бібліотеки типів. Детальніше про неї можна прочитати в MSDN, а російською мовою – в статті Ігоря Ткачова “Використання директиви # import в Visual C + +” .


При її використанні компілятор генерує опису інтерфейсів і, якщо не вказано інше, створює набір обгорткових класів (wrappers) для спрощення роботи з покажчиками на ці інтерфейси. Крім того, деталі реалізації COM-об’єктів ховаються за високорівневими засобами. У число таких деталей входять перетворення [out, retval]-параметрів у повертаються значення функцій, спрощення роботи з BSTR-рядками, управління термінами життя об’єктів, доступ до властивостей і перетворення COM-HRESULT у виключення С + +. Але підтримка всіх цих приємних “дрібниць” реалізована з використанням CRT і вимагає включення стартового коду CRT.


Директива # import, безсумнівно, корисна для C + +-програміста – адже інакше, не маючи опису інтерфейсів, довелося б виділяти необхідну інформацію вручну за допомогою утиліт типу OleView. Цю директиву можна застосовувати і в проектах, що не використовують CRT, але з низкою обмежень. Зокрема, необхідно придушити створення обгорткових класів і трансляцію типів COM в класи-обгортки _com_ptr, _com_error, _variant_t і _bstr_t. Ось приклад вивіреного використання # import, яке не “потягне” за собою половину коду CRT:


#import “file.dll” no_namespace, 
named_guids, no_implementation,
raw_interfaces_only, raw_dispinterfaces,
raw_native_types

Іноді при використанні # import можна обійтися “малою кров’ю”. Це можливо, наприклад, якщо в інтерфейсах імпортованої бібліотеки типів не використовуються BSTR-і VARIANT-параметри (взагалі-то, досить рідкісний випадок). Тоді можна скористатися всіма зручностями, наданими # import, але придушити генерацію винятків C + + при поверненні помилок. Для цього буде потрібно реалізувати функцію


void __stdcall _com_issue_error(HRESULT hr);

Така можливість визначається в кожному конкретному випадку експериментально. Все ж, якщо ви не використовуєте винятку, краще відмовитися від розширеної допомоги директиви # import і обробляти HRESULT вручну.


ПРИМІТКАУ складі вже згаданої бібліотеки ATL / AUX є засіб автоматичної генерації класів з бібліотек типів, яке більш придатне для надмалих проектів, ніж директива # import.

Використання обчислень з плаваючою точкою


У статтях, присвячених використанню макросу _ATL_MIN_CRT, часто кажуть, що в мінімальних ATL-проектах можна використовувати обчислення з плаваючою крапкою. На щастя, це не так. Вже давно минули часи, коли програма на C + + не могла стартувати без коду емуляції співпроцесора. Але труднощі таки залишилися, і їх доведеться обходити, тому:


постарайтеся використовувати fixed-арифметику замість floating point-обчислень


Це означає, що, наприклад, для розрахунку суми з точністю до копійок можна просто висловити суму в копійках. Інший варіант цієї методики – виділити кілька (наприклад, 16) двійкових розрядів числа на цілу частина, а решту вважати дробової. Тоді ціла частина числа виходить простим зсувом вправо або зверненням за такою адресою, що дуже швидко. Такі розрахунки здорово допоможуть там, де потрібні тільки цілі числа, але розраховані з хорошою точністю – наприклад, в машинній графіці.


Якщо floating-point обчислення необхідні, спробуйте обдурити компілятор за допомогою _fltused


Зустрівши в програмі оголошення float-змінної (або double), компілятор автоматично вставляє в генерований код зовнішнє посилання на змінну _fltused, Що знаходиться в одному з файлів CRT. Це робиться для того, щоб прілінковать до програми код обробника помилок обчислень з плаваючою крапкою.


У разі, коли не можна обійтися без плаваючою арифметики, але код свідомо не викличе помилок, можна спробувати відключити стартовий код CRT для плаваючих обчислень за допомогою приблизно такого оголошення:


extern “C” int _fltused = 0;

Мінлива виявляється певної всередині модуля, і лінкера нема чого шукати її деінде.


Але фокус не спрацює, якщо відбудеться виклик функції CRT. Це може статися неявно, наприклад, при перетворенні між цілочисельними і плаваючими типами:


double t;
int a;
a = t; / / Отримали зовнішнє посилання на функцію _ftol

Правда, _ftol – це якраз приклад функції CRT, яка може бути безболісно використана у мінімальній програмі. Просто вкажіть у списку бібліотек LIBC.LIB і подбайте про те, щоб забезпечити компонувальник своєю версією стартового коду (при використанні _ATL_MIN_CRT нічого додатково робити не потрібно).


Якщо ж викликається неявно функція вимагає ініціалізації, є два шляхи: відмовитися від боротьби або реалізувати її іншим способом, про що зараз і піде мова.


 

Кілька рекомендацій


Наостанок дам кілька порад, які допоможуть обійтися без стартового коду CRT у багатьох випадках. Але пам’ятайте, що це досягається за рахунок відмови від використання винятків, і працює не завжди. Як правило, починаючи з якогось обсягу коду, виграш від всіх цих хитрувань сходить нанівець, а незручності відмова від CRT доставляє як і раніше чималі.


Забудьте про це, якщо використовуєте MFC


Бібліотека MFC вимагає наявності коду ініціалізації, і тут уже нічого не поробиш. Якщо дуже хочеться використовувати бібліотеку віконних класів в надмалих проектах, подивіться в сторону ATL / WTL та їх розширень (Наприклад, Attila).


Використовуйте SEH замість C + + Exceptions


Обробка виключень у стилі C + + неминуче вимагатиме стартового коду CRT. Якщо виключення використовувати необхідно, спробуйте скористатися структурними винятками Win32 за допомогою ключових слів __ try, __except, __ finally і т.д. Для їх використання потрібно підключити бібліотеку імпорту kernel32.lib.


Спробуйте запозичити необхідну функцію з вихідних файлів CRT


Visual C + + поставляється з великим набором вихідних файлів, в число яких входить і реалізація CRT. Їх вивчення, до речі, приносить і ще одну вигоду – це допоможе розібратися, як саме влаштована підтримка стандартної бібліотеки. Загалом, “Use the source, Luke”!


Використовуйте директиву # pragma intrinsic


Деякі функції, що вимагають ініціалізації CRT, можуть бути просто вставлені компілятором в точку виклику. До них відносяться cos, strlen і багато інших. Вивчіть документацію на #pragma intrinsic і опцію компілятора /Oi.


Для перетворення типів скористайтеся Automation API


Це найпотужніше засіб перетворення даних різних типів нічого не коштуватиме – крім, хіба що, зайвих тактів процесора.


Можна використовувати як функції високого рівня VariantChangeType/VariantChangeTypeEx, Так і допоміжні функції перетворення виду Var XXX From YYY. У наведеному вище блоці коду, наприклад, допоможе функція VarI4FromR8:


double t;
long a;
VarI4FromR8 (t, & a); / / Жодних проблем із зовнішніми посиланнями

Використання Automation API дозволить не тільки вирішити проблему перетворення, описану вище, але і врахувати при цьому регіональні настройки – наприклад, отримати локалізовану рядок дати. Крім того, функція VarBstrCmp допоможе при порівнянні рядків Unicode (але будьте обережні з нею – в старих версіях Windows вона відрізняється від нових реалізацій, також потрібно мати встановлений Service Pack 4 або вище для VC, інакше заголовний файл буде містити некоректне опис цієї функції).


Для використання цих функцій необхідно підключити бібліотеку імпорту oleaut32.lib.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*