23

If have encountered this claim multiple times and can't figure out what it is supposed to mean. Since the resulting code is compiled using a regular C compiler it will end up being type checked just as much (or little) as any other code.

So why are macros not type safe? It seems to be one of the major reasons why they should be considered evil.

Sarien
  • 6,647
  • 6
  • 35
  • 55
  • 2
    The point is that C compiler does not deal with macro definitions. Preprocessor handles them – fatihk Jul 31 '13 at 12:41
  • 4
    Well, yes but if the generated code would actually violate the type system the compiler would catch that. – Sarien Jul 31 '13 at 12:42
  • 2
    The C++FAQ has solid coverage on why macros are evil here: http://www.parashift.com/c++-faq-lite/inline-vs-macros.html – Shafik Yaghmour Jul 31 '13 at 12:48
  • 1
    @Sarien: "*... if the generated code would actually violate the type system the compiler would catch that.*" You are absolutly correct. Moreover I'd say the statement "Macros aren't type-safe" does not make sense, as noone ever meant them to be type-safe or not, as they are only a helper to the C language and not an integral part of it. – alk Jul 31 '13 at 15:58
  • 3
    Broad claims that macros are not type safe come from people who have not thought it through. `#define NUM_REPS 3` is type safe; `NUM_REPS` expands to `3`, which has type `int`. As some of the answers have suggested, function-like macros can produce surprising results if you haven't thought about what they do; whether this rises to the level of not being "type safe" depends on exactly what you mean by "type safe". – Pete Becker Jul 31 '13 at 17:12

7 Answers7

40

Consider the typical "max" macro, versus function:

#define MAX(a,b) a < b ? a : b
int max(int a, int b) {return a < b ? a : b;}

Here's what people mean when they say the macro is not type-safe in the way the function is:

If a caller of the function writes

char *foo = max("abc","def");

the compiler will warn.

Whereas, if a caller of the macro writes:

char *foo = MAX("abc", "def");

the preprocessor will replace that with:

char *foo = "abc" < "def" ? "abc" : "def";

which will compile with no problems, but almost certainly not give the result you wanted.

Additionally of course the side effects are different, consider the function case:

int x = 1, y = 2;
int a = max(x++,y++); 

the max() function will operate on the original values of x and y and the post-increments will take effect after the function returns.

In the macro case:

int x = 1, y = 2;
int b = MAX(x++,y++);

that second line is preprocessed to give:

int b = x++ < y++ ? x++ : y++;

Again, no compiler warnings or errors but will not be the behaviour you expected.

Vicky
  • 12,934
  • 4
  • 46
  • 54
  • 10
    By this example, templates aren't type safe, either. A templated `max` function would allow exactly the same pointer-comparing behavior. The template, of course, would not fall prey to the multiple evaluation problem. – Joel Jul 31 '13 at 14:12
  • 2
    @Joel: the point of this answer is that a function (template) can be overloaded (or specialized) for a particular argument type, while the macro only ever has one implementation regardless of the argument type. – willj Jul 31 '13 at 14:44
  • 2
    This are nice thoughts around macros, but not really an answer to the question, aren't they? – alk Jul 31 '13 at 16:00
  • 1
    @willj: I agree that is one of the big reasons that proper language features are more type-safe than macros, but it's best to be explicit about that point, and it's also worth noting that even templates can let you do some silly things. FWIW, this post has made me rethink a few things I've written recently using templates :) – Joel Aug 01 '13 at 02:17
  • 2
    Am I missing something here, or does you max macro/function return the lower of their two arguments? – Damien_The_Unbeliever Aug 01 '13 at 06:12
  • @Damien_The_Unbeliever, yes, good catch :-) Substitute > instead of <. My main point still stands, though. – Vicky Aug 01 '13 at 11:35
  • @alk, I'm not sure why you think it's not an answer to the question - the question is "why do people say macros aren't type safe?" and I'm explaining why they are not type safe. I did throw in an extra bit about how they ALSO aren't side-effect safe which doesn't directly answer the question but expands upon it. – Vicky Aug 01 '13 at 11:37
  • 1
    From my point of view any answer here is not an answer to the question, as IMHO the question does not make sense, so no try to explain it would succeed per definition. So obviously I could have added my above comment to any of the answers given here ... – alk Aug 01 '13 at 11:47
11

Macros aren't type safe because they don't understand types.

You can't tell a macro to only take integers. The preprocessor recognises a macro usage and it replaces one sequence of tokens (the macro with its arguments) with another set of tokens. This is a powerful facility if used correctly, but it's easy to use incorrectly.

With a function you can define a function void f(int, int) and the compiler will flag if you try to use the return value of f or pass it strings.

With a macro - no chance. The only checks that get made are it is given the correct number of arguments. then it replaces the tokens appropriately and passes onto the compiler.

#define F(A, B)

will allow you to call F(1, 2), or F("A", 2) or F(1, (2, 3, 4)) or ...

You might get an error from the compiler, or you might not, if something within the macro requires some sort of type safety. But that's not down to the preprocessor.

You can get some very odd results when passing strings to macros that expect numbers, as the chances are you'll end up using string addresses as numbers without a squeak from the compiler.

Tom Tanner
  • 9,244
  • 3
  • 33
  • 61
10

Well they're not directly type-safe... I suppose in certain scenarios/usages you could argue they can be indirectly (i.e. resulting code) type-safe. But you could certainly create a macro intended for integers and pass it strings... the pre-processor handling the macros certainly doesn't care. The compiler may choke on it, depending on usage...

mark
  • 5,269
  • 2
  • 21
  • 34
  • 5
    This sounds good but somebody should point out to the world that this does not make macros in any way evil for generating could that would otherwise have to be duplicated. :) – Sarien Jul 31 '13 at 12:50
  • 3
    I once worked on a project where a co-worker #define'd printf to be something else, in one of the core header files. – Graham Griffiths Jul 31 '13 at 12:55
4

Since macros are handled by the preprocessor, and the preprocessor doesn't understand types, it will happily accept variables that are of the wrong type.

This is usually only a concern for function-like macros, and any type errors will often be caught by the compiler even if the preprocessor doesn't, but this isn't guaranteed.

An example

In the Windows API, if you wanted to show a balloon tip on an edit control, you'd use Edit_ShowBalloonTip. Edit_ShowBalloonTip is defined as taking two parameters: the handle to the edit control and a pointer to an EDITBALLOONTIP structure. However, Edit_ShowBalloonTip(hwnd, peditballoontip); is actually a macro that evaluates to

SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)(peditballoontip));

Since configuring controls is generally done by sending messages to them, Edit_ShowBalloonTip has to do a typecast in its implementation, but since it's a macro rather than an inline function, it can't do any type checking in its peditballoontip parameter.

A digression

Interestingly enough, sometimes C++ inline functions are a bit too type-safe. Consider the standard C MAX macro

#define MAX(a, b) ((a) > (b) ? (a) : (b))

and its C++ inline version

template<typename T>
inline T max(T a, T b) { return a > b ? a : b; }

MAX(1, 2u) will work as expected, but max(1, 2u) will not. (Since 1 and 2u are different types, max can't be instantiated on both of them.)

This isn't really an argument for using macros in most cases (they're still evil), but it's an interesting result of C and C++'s type safety.

Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
  • The Windows API example is a good one of something looks like a function that takes arguments without casts but in reality allows you to pass almost any garbage. – Joel Jul 31 '13 at 14:14
  • "*... it will happily accept variables that are of the wrong type.*" I feel this statement does not make sense, as macro parameters are untyped, so there are no wrong or right types. – alk Jul 31 '13 at 16:20
  • 2
    @alk: Isn't that the point? There *certainly* are wrong types, in the same sense that any untyped language can have a function that's given data in an unexpected format. No types, no type safety. That's the answer to the question here. – Phoshi Jul 31 '13 at 16:54
  • its pretty easy to define a max template that can compare different types if thats what you want. but i'd actually say that comparing unsigned and signed is wrong so its a good thing you get an error. no such thing as too much type safety – jk. Jul 31 '13 at 21:25
  • @jk. - max(long, int) or max(double, int) if you prefer. Determining the return type to use for a max template that compares different types isn't easy (unless I'm missing something). http://www.drdobbs.com/generic-min-and-max-redivivus/184403774 is interesting reading on the topic. – Josh Kelley Jul 31 '13 at 21:39
2

There are situations where macros are even less type-safe than functions. E.g.

void printlog(int iter, double obj)
{
    printf("%.3f at iteration %d\n", obj, iteration);
}

Calling this with the arguments reversed will cause truncation and erroneous results, but nothing dangerous. By contrast,

#define PRINTLOG(iter, obj) printf("%.3f at iteration %d\n", obj, iter)

causes undefined behavior. To be fair, GCC warns about the latter, but not about the former, but that's because it knows printf -- for other varargs functions, the results are potentially disastrous.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
1

When the macro runs, it just does a text match through your source files. This is before any compilation, so it is not aware of the datatypes of anything it changes.

Graham Griffiths
  • 2,196
  • 1
  • 12
  • 15
  • 1
    As you said it is done before compilation, which is where the types are checked, that's exactly what has me confused. – Sarien Jul 31 '13 at 12:44
  • 1
    it's true that the code will all be compiled through the compiler with the usual type system. But the macro expansion step itself does not respect data-types - it can't since it happens before compilation is started – Graham Griffiths Jul 31 '13 at 12:46
  • in particular your MSDN link talks about function macros (like here : http://stackoverflow.com/questions/163365/how-do-i-make-a-c-macro-behave-like-a-function ) the point is - it recognizes two inputs and adds them together, but without carefully checking their types in the way a compiler would do – Graham Griffiths Jul 31 '13 at 12:47
  • for example I could put in MACRO_ADD( orange, 1.5 ) and it would happily generate "orange + 1.5". Of course you are correct that this code will fail later on, during compilation. But it passes happily through the macro expansion, which is why we can say the macro expansion is not type safe. – Graham Griffiths Jul 31 '13 at 12:49
1

Macros aren't type safe, because they were never meant to be type safe.

The compiler does the type checking after macros had been expanded.

Macros and there expansion are meant as a helper to the ("lazy") author (in the sense of writer/reader) of C source code. That's all.

alk
  • 69,737
  • 10
  • 105
  • 255
  • The same is true for C++ templates. The compiler does type checking only *after* templates have been expanded (instantiated). So macros are not any less type safe than C++ templates. Both are indirectly type-safe, because the compiler *will* eventually check types in the finally generated code. Neither is directly type-safe, that is neither C macros not C++ template *definitions* are type checked. – Piotr Kołaczkowski Mar 27 '16 at 08:38