45

I know that they are implemented extremely unsafely in C/C++. Can't they be implemented in a safer way? Are the disadvantages of macros really bad enough to outweigh the massive power they provide?

Casebash
  • 7,672
  • 5
  • 42
  • 62

7 Answers7

67

I think the main reason is that macros are lexical. This has several consequences:

  • The compiler has no way of checking that a macro is semantically closed, i.e. that it represents a “unit of meaning” like a function does. (Consider #define TWO 1+1 — what does TWO*TWO equal? 3.)

  • Macros are not typed like functions are. The compiler cannot check that the parameters and return type make sense. It can only check the expanded expression that uses the macro.

  • If the code doesn’t compile, the compiler has no way of knowing whether the error is in the macro itself or the place where the macro is used. The compiler will either report the wrong place half of the time, or it has to report both even though one of them is probably fine. (Consider #define min(x,y) (((x)<(y))?(x):(y)): What should the compiler do if the types of x and y don’t match or don’t implement operator<?)

  • Automated tools cannot work with them in semantically useful ways. In particular, you can’t have things like IntelliSense for macros that work like functions but expand to an expression. (Again, the min example.)

  • The side-effects of a macro are not as explicit as they are with functions, causing potential confusion for the programmer. (Consider again the min example: in a function call, you know that the expression for x is evaluated only once, but here you can’t know without looking at the macro.)

Like I said, these are all consequences of the fact that macros are lexical. When you try to turn them into something more proper, you end up with functions and constants.

Timwi
  • 4,459
  • 30
  • 37
16

Macros can, as Scott notes, allow you hide logic. Of course, so do functions, classes, libraries, and many other common devices.

But a powerful macro system can go further, enabling you to design and utilize syntax and structures not normally found in the language. This can be a wonderful tool indeed: domain-specific languages, code generators and more, all within the comfort of a single language environment...

However, it can be abused. It can make code harder to read, understand and debug, increase the time necessary for new programmers to become familiar with a codebase, and lead to costly mistakes and delays.

So for languages intended to simplify programming (like Java or Python), such a system is an anathema.

Shog9
  • 8,101
  • 2
  • 46
  • 56
15

But yes, macros can be designed and implemented better than in C/C++.

The problem with macros is that they are effectively a language syntax extension mechanism that rewrites your code into something else.

  • In the C / C++ case, there is no fundamental sanity checking. If you are careful, things are OK. If you make a mistake, or if you overuse macros you can get into big problems.

    Add to this that many simple things you can do with (C/C++ style) macros can be done in other ways in other languages.

  • In other languages such as various Lisp dialects, macros are better integrated with the core language syntax, but you can still get problems with declarations in a macro "leaking". This is addressed by hygienic macros.


Brief Historical Context

Macros (short for macro-instructions) first appeared in the context of assembly language. According to Wikipedia, macros were available in some IBM assemblers in the 1950s.

The original LISP didn't have macros, but they were first introduced into MacLisp in the mid 1960s: https://stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code-transformation-appear. http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf. Prior to that, "fexprs" provided macro-like functionality.

The earliest versions of C didn't have macros (http://cm.bell-labs.com/cm/cs/who/dmr/chist.html). These were added circa 1972-73 via a preprocessor. Prior to that, C only supported #include and #define.

The M4 macro-preprocessor originated in circa 1977.

More recent languages apparently implement macros where the model of operation is syntactic rather than textual.

So when someone talks about the primacy of a particular definition of the term "macro", it is important to note that the meaning has evolved over time.

Stephen C
  • 25,388
  • 6
  • 66
  • 89
7

Macros can be implemented very safely in some circumstances - in Lisp for example, macros are just functions that return transformed code as a data structure (s-expression). Of course, Lisp benefits significantly from the fact that it is homoiconic and the fact that "code is data".

An example of how easy macros can be is this Clojure example which specifies a default value to be used in the case of an exception:

(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

Even in Lisps though, the general advice is "don't use macros unless you have to".

If you aren't using a homoiconic language, then macros get much trickier and the various other options all have some pitfalls:

  • Text-based macros - e.g. the C preprocessor - simple to implement but very tricky to use correctly as you need to generate the correct source syntax in textual form, including any syntactical quirks
  • Macro-based DSLS - e.g. the C++ template system. Complex, can itself result in some tricky syntax, can be extremely complex for compiler and tool writers to handle correctly since it introduces significant new complexity into the language syntax and semantics.
  • AST/bytecode manipulation APIs - e.g. Java reflection / bytecode generation - theoretically very flexible but can get very verbose: it can require a lot of code to do quite simple things. If it takes ten lines of code to generate the equivalent of a three line function, then you haven't gained much with your meta-programming endeavours...

Furthermore, everything a macro can do can ultimately be achieved in some other way in a turing complete language (even if this means writing a lot of boilerplate). As a result of all this trickiness, it's not surprising that many languages decide that macros aren't really worth the all effort to implement.

mikera
  • 20,777
6

To answer your questions, think about what macros are predominantly used for (Warning: brain-compiled code).

  • Macros used to define symbolic constants #define X 100

This can easily be replaced with: const int X = 100;

  • Macros used to define (essentially) inline type-agnostic functions #define max(X,Y) (X>Y?X:Y)

In any language that supports function overloading, this can be emulated in a much more type-safe manner by having overloaded functions of the correct type, or, in a language that supports generics, by a generic function. The macro will happily attempt to compare anything including pointers or strings, which might compile, but is almost certainly not what you wanted. On the other hand, if you made macros type-safe, they offer no benefits or convenience over overloaded functions.

  • Macros used to specify shortcuts to often-used elements. #define p printf

This is easily replaced by a function p() that does the same thing. This is quite involved in C (requiring you to use the va_arg() family of functions) but in many other languages that support variable numbers of function arguments, it is much simpler.

Supporting these features within a language rather than in a special macro language is simpler, less error prone and far less confusing to others reading the code. In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way. The only place where macros are truly useful is when they are tied to conditional compilation constructs like #if (etc.).

On that point, I won't argue with you, since I believe that non-preprocessor solutions to conditional compilation in popular languages are extremely cumbersome (like bytecode injection in Java). But languages like D have come up with solutions that do not require a preprocessor and are no more cumbersome than using preprocessor conditionals, while being far less error-prone.

Chinmay Kanchi
  • 6,173
  • 2
  • 40
  • 51
2

Lest start by noting that MACROs in C/C++ are very limited, error prone, and not really that useful.

MACROs as implemented in say LISP, or z/OS assembler language can be reliable and incredibly useful.

But because of the abuse of the limited functionality in C they have a gathered a bad reputation. So nobody implements macros any more, instead you get things like Templates which do some of the simple stuff macros used to do and things like Java's annotations which do some of the more complex stuff macro's used to do.

2

The biggest problem I have seen with macros is that when heavily used they can make code very difficult to read and maintain since they allow you to hide logic in the macro that may or may not be easy to find (and may or may not be trivial).

Scott Dorman
  • 1,479
  • 1
  • 11
  • 8