Чому макроси не включені в більшість сучасних мов програмування?


31

Я знаю, що вони реалізуються надзвичайно небезпечно в C / C ++. Хіба вони не можуть бути реалізовані більш безпечно? Чи недоліки макросів справді досить погані, щоб переважати масивну потужність, яку вони надають?


4
Яку потужність надають макроси, які неможливо досягти досить легко по-іншому?
Chinmay Kanchi

2
У C # знадобилося розширення мови основної мови, щоб обернути створення простого властивості та резервного поля в одну декларацію. І це займає більше, ніж лексичні макроси: як можна створити функцію, відповідну WithEventsмодифікатору Visual Basic ? Вам знадобиться щось на зразок семантичних макросів.
Джефрі Хантін

3
Проблема з макросами полягає в тому, що, як і всі потужні механізми, він дозволяє програмісту порушити припущення, які зробили або зроблять інші. Припущення є ключем до міркувань і без можливості поважно міркувати про логіку, а досягнення прогресу стає непосильним.
dan_waterworth

2
@Chinmay: макроси генерують код. На мові Java з такою потужністю немає жодної функції.
кевін клайн

1
@Chinmay Kanchi: Макроси дозволяють виконувати (оцінювати) код під час компіляції, а не під час виконання.
Джорджіо

Відповіді:


57

Я думаю, що головна причина - макроси лексичні . Це має кілька наслідків:

  • У компілятора немає можливості перевірити, чи є макрос семантично закритим, тобто що він являє собою "одиницю значення", як це робить функція. (Поміркуйте #define TWO 1+1- що TWO*TWOдорівнює? 3.)

  • Макроси не набираються, як і функції. Компілятор не може перевірити, чи мають параметри та тип повернення сенс. Він може перевірити лише розширений вираз, який використовує макрос.

  • Якщо код не компілюється, компілятор не може знати, чи є помилка в самому макросі або в тому місці, де використовується макрос. Компілятор або повідомить про неправильне місце половину часу, або повинен повідомити про обидва, навіть якщо один з них, ймовірно, добре. (Поміркуйте #define min(x,y) (((x)<(y))?(x):(y)): що повинен робити компілятор, якщо типи xта yне відповідають або не реалізуються operator<?)

  • Автоматизовані інструменти не можуть працювати з ними семантично корисними способами. Зокрема, ви не можете мати такі речі, як IntelliSense для макросів, які працюють як функції, але розширюються на вираз. (Знову minприклад.)

  • Побічні ефекти макроса не настільки явні, як з функціями, що викликає потенційну плутанину у програміста. (Розглянемо ще раз minприклад: у виклику функції ви знаєте, що вираз для xоцінюється лише один раз, але тут ви не можете знати, не дивлячись на макрос.)

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


32
Не всі макро мови є лексичними. Наприклад, макроси схеми є синтаксичними, а шаблони C ++ - семантичними. Лексичні макросистеми відрізняються тим, що їх можна встановити на будь-якій мові, не усвідомлюючи її синтаксису.
Джеффрі Хантін

8
@ Джефрі: Я думаю, що мої "макроси лексичні" - це справді скорочення "коли люди посилаються на макроси мовою програмування, вони, як правило, думають про лексичні макроси". Прикро, що Схема використовує цей завантажений термін для чогось принципово іншого. Однак шаблони C ++ широко не називають макросами, імовірно, саме тому, що вони не є повністю лексичними.
Тімві

25
Я думаю, що використання терміна макрос схемою датується Ліспом, що, ймовірно, означає, що воно передує більшості інших застосувань. IIRC система C / C ++ спочатку називалася препроцесором .
Беван

16
@Bevan має рацію. Скажіть, що макроси лексичні - це як сказати, що птахи не можуть літати, тому що птах, яку ви найбільше знайомі, - це пінгвін. Однак, більшість (але не всі) балів, які ви набираєте, стосуються також синтаксичних макросів, хоча, можливо, і в меншій мірі.
Лоранс Гонсальвес

13

Але так, макроси можна розробити та реалізувати краще, ніж у C / C ++.

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

  • У випадку C / C ++ немає принципової перевірки рівня безпеки. Якщо ви обережні, все в порядку. Якщо ви зробили помилку, або якщо ви зловживаєте макросами, ви можете потрапити у великі проблеми.

    Додайте до цього, що багато простих речей, які ви можете зробити з макросами (стиль C / C ++), можна зробити іншими способами іншими мовами.

  • В інших мовах, таких як різноманітні діалекти Lisp, макроси краще інтегруватися з синтаксисом основної мови, але ви все ще можете отримати проблеми з деклараціями в макросі "протікання". З цим вирішуються гігієнічні макроси .


Короткий історичний контекст

Макроси (скорочення макроінструкцій) вперше з'явилися в контексті мови складання. Згідно з Вікіпедією , макроси були доступні в деяких складальниках IBM в 1950-х роках.

Оригінальний LISP не макросів, але вони були вперше введені в MacLisp в середині 1960-х: https://stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code -трансформація-з’являються . http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf . До цього "fexprs" забезпечували макроподібну функціональність.

Найдавніші версії C не мали макросів ( http://cm.bell-labs.com/cm/cs/who/dmr/chist.html ). Вони були додані приблизно в 1972-73 рр. За допомогою препроцесора. До цього C підтримував лише #includeта #define.

Макропропроцесор M4 зародився приблизно в 1977 році.

Більш новітні мови, мабуть, реалізують макроси, де модель роботи є синтаксичною, а не текстовою.

Тож коли хтось говорить про першість певного визначення терміна «макро», важливо зазначити, що значення розвивалося з часом.


9
C ++ є благословенним (?) З ДВОМИ макросистемами: препроцесором та розширенням шаблонів.
Джефрі Хантін

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

@Dunk, визначення макросу, як у Lisp, передує "сучасному" тупому розумінню, як у жалюгідному препроцесорі C.
SK-логіка

@SK: Чи краще бути "технічно" правильним і придумати розмову чи краще це зрозуміти?
Данк

1
@Dunk, термінологією не належать неосвічені орди. Їх незнання ніколи не слід враховувати, інакше це призведе до скидання всієї галузі інформатики. Якщо хтось розуміє "макрос" лише як посилання на препроцесор C, це не що інше, як незнання. Я сумніваюсь, що значна частина людей, які ніколи не чули про Lisp або навіть про, pardonnez mon français, VBA "макроси" в офісі.
SK-логіка

12

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

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

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

Отже, для мов, призначених для спрощення програмування (наприклад, Java або Python), така система є анафемою.


3
І тоді Java пішла з рейок, додавши передбачення, анотації, твердження,
загальні затримки

2
@ Джефрі: і не будемо забувати, безліч генераторів сторонніх кодів. По-друге, забудьмо про це.
Shog9

4
Ще один приклад того, як Java була спрощеною замість простої.
Джефрі Хантін

3
@JeffreyHantin: Що не так з foreach?
Casebash

1
@Dunk Ця аналогія може бути розтяжкою ... але я думаю, що розширюваність мови схожа на плутоній. Дорога у виконанні, дуже складна в обробці потрібних форм і надзвичайно небезпечна при неправильному використанні, але також надзвичайно потужна при хорошому застосуванні.
Джефрі Хантін

6

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

Прикладом того, наскільки легкими можуть бути макроси, є цей приклад Clojure, який визначає значення за замовчуванням, яке слід використовувати у випадку винятку:

(defmacro on-error [default-value code]
  `(try ~code (catch Exception ~'e ~default-value)))

(on-error 0 (+ nil nil))               ;; would normally throw NullPointerException
=> 0                                   ;l; but we get the default value

Навіть у Lisps, однак, загальна порада "не використовуйте макроси, якщо не потрібно".

Якщо ви не використовуєте гомонічну мову, макроси стають набагато складнішими, а всі інші параметри мають деякі підводні камені:

  • Текстові макроси - наприклад, препроцесор C - простий у здійсненні, але дуже складний у використанні, оскільки потрібно створити правильний синтаксис у текстовій формі, включаючи будь-які синтаксичні химерності
  • DSLS на основі макросу - наприклад, система шаблонів C ++. Складний, що сам по собі може спричинити складний синтаксис, може бути надзвичайно складним для правильного поводження з компіляторами та інструментами, оскільки він вносить значну нову складність у синтаксис та семантику мови.
  • API для маніпулювання AST / байт-кодом - наприклад, відображення / генерація байт-коду - теоретично дуже гнучка, але може отримати дуже багатослівний: для виконання досить простих речей може знадобитися багато коду. Якщо для генерування еквівалента функції трьох рядків потрібно десять рядків коду, то ви не зробили багато своїх мета метапрограмування ...

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


Тьфу. Не більше "код - це дані - це добре" сміття. Код - це код, дані - це дані, а якщо їх належним чином відокремити - це дірка безпеки. Ви можете подумати, що поява SQL Injection як одного з найбільших існуючих класів вразливості дискредитувала б ідею зробити код і дані легко взаємозамінними раз і назавжди.
Мейсон Уілер

10
@Mason - Я не думаю, що ви розумієте концепцію коду-це-дані. Весь вихідний код програми C також є даними - це, як правило, виражається у текстовому форматі. Липи ті ж, за винятком того, що вони виражають код у практичних проміжних структурах даних (s-вирази), які дають змогу маніпулювати та трансформувати макроси перед компіляцією. В обох випадках надсилання ненадійного вводу компілятору може бути дією для безпеки - але це важко зробити випадково, і ви будете винні в тому, що робите щось дурне, а не компілятор.
mikera

Іржа - ще одна цікава безпечна макросистема. Він має суворі правила / семантику, щоб уникнути різновидів проблем, пов'язаних з лексичними макросами C / C ++, і використовує DSL для зйомки частин вхідного вираження в макро змінних, які потрібно вставити пізніше. Це не настільки потужно, як здатність Ліпса викликати будь-яку функцію на вході, але вона забезпечує великий набір корисних макросів маніпулювання, які можна викликати з інших макросів.
zstewart

Тьфу. Не більше сміття для безпеки . В іншому випадку звинувачуйте кожну систему із вбудованою архітектурою фон Неймана, наприклад, кожен процесор IA-32 з можливістю самовиправлення коду. Я підозрюю, чи можете ви заповнити діру фізично ... У будь-якому випадку, вам доведеться стикатися з фактом взагалі: ізоморфізм між кодом і даними - це природа світу кількома способами . І, напевно, саме ви, програміст, зобов’язані зберігати інваріантність вимог безпеки, що не є штучним застосуванням передчасної сегрегації де завгодно.
FrankHB

4

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

  • Макроси, що використовуються для визначення символічних констант #define X 100

Це легко замінити на: const int X = 100;

  • Макроси, що використовуються для визначення (по суті) вбудованих типово-агностичних функцій #define max(X,Y) (X>Y?X:Y)

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

  • Макроси, що використовуються для вказівки ярликів до часто використовуваних елементів. #define p printf

Це легко замінюється функцією, p()яка робить те саме. Це досить задіяно в C (вимагає використання va_arg()сімейства функцій), але в багатьох інших мовах, які підтримують змінну кількість аргументів функції, це набагато простіше.

Підтримка цих функцій в рамках мови , а не в спеціальному макромові простіше, менш схильна до помилок і набагато менш заплутане інше читанні коди. Насправді я не можу придумати єдиного випадку використання для макросів, які не можна легко дублювати іншим способом. Тільки місце , де макроси дійсно корисні, коли вони прив'язані до умовної компіляції конструкцій , таких як #if( і т.д.).

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


1
якщо вам доведеться #define max, поставте дужки навколо параметрів, щоб не було несподіваних ефектів від пріоритету оператора ... наприклад, #define max (X, Y) ((X)> (Y)? (X) :( Y))
foo

Ви розумієте, що це був лише приклад ... Намір був проілюструвати.
Chinmay Kanchi

+1 для цього систематичного розгляду справи. Мені подобається додати, що умовна компіляція може легко стати кошмаром - я пам'ятаю, що існувала програма під назвою "unifdef" (??), метою якої було зробити видно те, що залишилося після післяобробки.
Інго

7
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way: щонайменше на C, ви не можете формувати ідентифікатори (імена змінних), використовуючи з'єднання токенів без використання макросів.
Чарльз Сальвія

Макроси дозволяють гарантувати, що певні структури коду та даних залишаються "паралельними". Наприклад, якщо існує невелика кількість умов із пов’язаними повідомленнями, і потрібно буде зберігати їх у стислому форматі, спроба використовувати a enumдля визначення умов та постійний масив рядків для визначення повідомлень може призвести до проблем, якщо enum і масив виходять із синхронізації. Використання макросу для визначення всіх пар (enum, string), а потім включення цього макросу двічі з іншими правильними визначеннями в область застосування кожного разу дозволить поставити кожне значення enum поруч із його рядком.
supercat

2

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


Чи не повинен бути задокументований макрос?
Casebash

@Casebash: Звичайно, макрос повинен бути задокументований так само, як і будь-який інший вихідний код ... але на практиці я його рідко бачив.
Скотт Дорман

3
Ви коли-небудь налагоджували документацію? Це просто недостатньо при роботі з погано написаним кодом.
JeffO

У OOP теж є деякі з цих проблем, хоча ...
aoeu256

2

Для початку слід зазначити, що MACRO в C / C ++ дуже обмежені, схильні до помилок і не дуже корисні.

MACRO, реалізовані в скажімо LISP, або мові асемблера z / OS, можуть бути надійними та неймовірно корисними.

Але через зловживання обмеженою функціональністю у C вони зібралися поганою репутацією. Тож ніхто більше не реалізовує макроси, натомість ви отримуєте такі речі, як Шаблони, які роблять деякі найпростіші макроси, які використовувались, і такі речі, як анотації Java, які роблять деякі більш складні речі, які макрос використовував.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.