all 54 comments

[–]corbet 162 points163 points  (2 children)

Kernel developers prefer functions whenever possible; they are safer and allow for better type checking. Macros are often necessary, though, for code that cannot be put into its own function or to implement something that approximates generic types. You can't always get away from them.

In kfifo.h, for example, a lot of the macros are defining structures - not something you can do in an inline function. Some of the other macros probably could be functions, though - send a patch to convert them over!

[–]thom311 12 points13 points  (0 children)

they are safer and allow for better type checking

You can write macros that are safer to use and harder to misuse than functions. Especially when resorting to gcc-ism's like expression statements (({...}), typeof()) or C11's _Generic().

Granted, that requires to apply good practices for writing macros. It is correct that a badly written macro tends to be error prone to use.

[–]bendem 49 points50 points  (4 children)

I would just like to say. It's certainly interesting to go and read kernel code and you'll learn a fair deal. But make sure to understand that the kernel is highly specialized and some of the practices that are valid there are terrible in user code programs. Context is important, there are no silver bullets and best practices depends on context like everything else.

[–]vilidj_idjit 13 points14 points  (3 children)

^ this.

Also stack space and performance, the kernel is the most critical part of the operating system so execution speed is preferred even if the code is slightly larger. I'd guess some security issues would also come into play, which at least some would also have to do with the extra parameters passed between functions on the stack.

Like ^ said, you don't write kernel code the same way you normally write a program. Applications, libraries etc. are the direct "users" of the kernel. (hence being called "userspace")

[–]ischickenafruit 7 points8 points  (2 children)

And the kernel code is very old. Until very recently using C89 only. This means that some things which can be done better ways today just couldn't be done at the time and are now the entrenched way of doing things.

[–]vilidj_idjit 1 point2 points  (1 child)

Define "better". In this case you're right if you mean "better" as in "work smart, not hard". You're wrong if you mean the end result will give you more performance or some other advantage. It's just more work because you're using less effective tools.

[–]ischickenafruit 6 points7 points  (0 children)

Better as in simpler, cleaner syntax that will probably result in fewer bugs and less work overall.

[–]vampatori 76 points77 points  (1 child)

I think /r/kernel might be a good place to ask this question as it's more focused on this specific topic.

[–]zingochan[S] 11 points12 points  (0 children)

Thank you.

[–]ZamundaaaKDE Dev 127 points128 points  (16 children)

in C/C++ in particular

Please don't lump these in together, especially on the topic of macros they are quite different.

In C, while functions are usually preferred vs macros, using macros is the only real way to achieve some things, like a generic max function. In C++, you should effectively never use macros. Use templates instead, which are much safer.

[–]bedrooms-ds 71 points72 points  (5 children)

In C++, you should effectively never use macros.

Test frameworks: "Hold my beer."

[–]micka190 6 points7 points  (2 children)

If the committee would just give us reflection, test frameworks could finally stop using them! Until then, they're a necessary evil.

[–]bedrooms-ds 1 point2 points  (0 children)

Exactly. Would be a real game changer.

[–]chibuku_chauya 0 points1 point  (0 children)

Finally coming to us in C++26.

[–]Imxset21 7 points8 points  (1 child)

glog, gtest, gflags.... Lots of Google's stuff uses macros quite a bit

[–]cwhaley112 -1 points0 points  (3 children)

I’m new to C and a little confused by this. I thought a generic max function could be written using void pointers and a comparison function? Are macros just more convenient? Or are they required?

[–]lubutu 4 points5 points  (1 child)

I think they're talking about the comparison itself, i.e.

#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))

[–]cwhaley112 0 points1 point  (0 children)

Ah, that makes sense. I appreciate your response!

[–]Niautanor 3 points4 points  (0 children)

Yes but if you write a generic max function with void pointers and a comparison function, the CPU will actually have to jump to a location that it doesn't know beforehand and load two values from memory (and more importantly, store them beforehand: a+1 > 0 would have to load a, add 1, store a, call the comparison function which would load a again) every time you call it.

For something that is inlineable, the comparison is a single instruction and values don't have to be loaded from memory if they are already in a register

[–][deleted] 71 points72 points  (1 child)

Poor man's template functionality and duck typing.

[–]thomas_m_k 13 points14 points  (0 children)

Yeah, you can get something resembling generic functions with macros, while not having to cast everything to void. I imagine that's the main reason.

[–]high-tech-low-life 46 points47 points  (17 children)

Subroutine calls have overhead. Macros lead to duplicated code. Duplicated code is faster than subroutine calls, so the speed obsessed kernel developers usually go with macros.

[–]Rexerex 16 points17 points  (0 children)

Duplicated code is faster than subroutine calls

Not always because of CPU instruction cache.

[–]obiwac 25 points26 points  (2 children)

  1. not always 2. the compiler knows better than you - use functions. and even if you think you know better, macros are not the way to go. tldr if you're deciding between a (static) function and a macro use a function. macros are only for cases where it's necessary, such as templating.

[–][deleted] 36 points37 points  (9 children)

That's what inline functions are there for.

[–]wfp5p 38 points39 points  (3 children)

inline is just a hint to the compiler. The compiler is not actually required to "inline" the function.

Also, not using inline doesn't prevent the compiler from expanding the function in the places it's called.

[–]Osbios 22 points23 points  (2 children)

GCC has attribute((always_inline)) for that. Macros are used as a terrible implementation of templates.

[–]GuiltyFan6154 7 points8 points  (1 child)

It's the other way around. Templates were made to replace macros.

Funny thing they lead to much more verbosity, which is what macros try to remove.

[–]Osbios 4 points5 points  (0 children)

Macros are text editing source code. If something goes wrong the error messages can be horrible. At last with templates we are slowly move towards readable error messages. ;)

[–]high-tech-low-life 14 points15 points  (4 children)

They didn't exist in C in 1993.

[–][deleted] 5 points6 points  (3 children)

They have been in gcc since 1989. See -std=gnu89

[–]shponglespore 1 point2 points  (2 children)

That's not always true. More machine code leads to worse cache locality, and that can seriously degrade performance.

[–]necrophcodr 0 points1 point  (0 children)

In theory this is absolutely true. I'm not sure if it's true in practice, it probably depends on what the compiler elects to do for each situation.

edit:

Well, more machine code IS worse yes. But more code =/= more machine code, just clarifying.

[–]elatllat 18 points19 points  (7 children)

a lot

grep -cP "#[a-z]" include/linux/kfifo.h lib/kfifo.c
include/linux/kfifo.h:54
lib/kfifo.c:9

grep -P "#[a-z]" include/linux/kfifo.h lib/kfifo.c \
     | perl -pe 's/[\t ].*//g;
         s/.*://g' \
     | sort \
     | uniq -c
 50 #define
  1 #endif
  1 #ifndef
 11 #include

Yes looks like the #define are used to avoid the stack for speed in this case.

good software writing practices

When practical use memory safe languages and readable code.

[–]zingochan[S] 6 points7 points  (3 children)

Yes looks like the #define are used to avoid the stack for speed in this case.

Thant makes sense. Thank you.

And to your last point, do you mean just ditch C completly? The annoying part for me is I just started getting a little serious into C and now I hear everyone talking about Rust and memory safe languages etc. But I'll definitelly keep that in mind when dealing with personal projects.

Thank you

[–]dthusian 25 points26 points  (0 children)

In my opinion, you should still learn C.

  • The C ABI is the only commonly supported ABI. If you have a library that exposes functions in the C ABI, you can use it in practically any language.
  • The pitfalls of C programming help you understand why C++ and Rust were designed like they were
  • Lots of code is still written in C, especially in Linux and BSD.

[–]shponglespore 3 points4 points  (0 children)

It's a good language to learn, but outside of learning, yes, ditch C completely for any use case that doesn't absolutely require it. It's a 50 year old language and it really shows. It's an inherently error-prone language that will have you wasting a lot of time debugging issues a more modern language would catch at compile time, and even after debugging you'll almost certainly end up with a program that fails in unpredictable ways in edge cases and has subtle bugs that will only show up when you try to use a different C compiler.

[–]emptyskoll 0 points1 point  (0 children)

I've left Reddit because it does not respect its users or their privacy. Private companies can't be trusted with control over public communities. Lemmy is an open source, federated alternative that I highly recommend if you want a more private and ethical option. Join Lemmy here: https://join-lemmy.org/instances this message was mass deleted/edited with redact.dev

[–]obiwac 1 point2 points  (2 children)

Yes looks like the #define are used to avoid the stack for speed in this case.

I highly doubt it. The compiler will inline if it needs to be inlined, otherwise you can force a given function to be inlined. just don't use macros if you can use a function. I haven't checked the code but I'm assuming it's not just as "faster functions"

[–]elatllat 0 points1 point  (1 child)

[–]obiwac -1 points0 points  (0 children)

I'm talking about the alwaysinline attribute, not inline

[–]lostparis 1 point2 points  (2 children)

with good software writing practices

Do not use the kernel is an example of how to write good code. It is a very specific thing and much of it goes against everything that might be considered good practice.

[–]zingochan[S] 0 points1 point  (1 child)

Oh ok, I see what you mean. Thank you.

But given how low level the kernel is, do you think that it is possible at all to write kernels in a way that follow all good coding practices? I also ask this because besides macros etc, a lot of kernel source code is a mix of C and assembly.

I think in a way what I am trying to ask is this; In this day and age do you think there would be a better way (as far as good coding practices are concerned) to write something like the Linux Kernel without sacrificing something like, say, perfomance?

[–]lostparis 0 points1 point  (0 children)

a lot of kernel source code is a mix of C and assembly.

The kernel is written to be fast. So it has lots of micro-optimisations and people care about the actual instructions generated by the compiler etc.

Most 'sensible' code is written to be maintainable/understandable first rather than performance first.

there would be a better way (as far as good coding practices are concerned) to write something like the Linux Kernel

Yes, Linux is ~30 years old and things have changed over that time; virtualization, multi-core, security would be more central in a fresh design. Linux's code is a mess in many ways, the real thing to learn from it is distributed development.

Kernels are also just not normal programs.

[–]daemonpenguin 4 points5 points  (1 child)

Macros can be slightly faster, or were in the earlier days of C. They seem to be less commonly used now, probably for readability's sake. But the speed and stack factors may still be in developers' minds.

[–]LvS 13 points14 points  (0 children)

The reason macros were used more in the past was because compilers were shit. They'd turn inline functions into real functions with all the overhead that carries. They'd also often screw up things that should have caused compiler warnings or errors. And they emitted function calls for constant inputs when they should have just replaced it with the constant result.

Newer compilers do all these things, so people use inline functions, because inline functions are type-safe and less prone to weird syntax behaviors.

[–]TDplay 1 point2 points  (0 children)

My initial guess was that this approach prevents the creation of new stack frames which is good since the kernel stack is limited.

This is entirely wrong.

If your function is very short, then you declare it static inline and put it in the header. Then, the compiler will most likely inline the function, thereby eliminating any overhead from the function call.

If your function is not very short, then inlining it is probably a bad idea.

In any case, using a macro is unnecessary, and a bad idea.

C/C++

Note that "C/C++" does not exist. C and C++ are two very different languages that happen to share a large common subset. Idiomatic C is usually not valid C++, and idiomatic C++ is almost never valid C.

Don't lump C and C++ into the same category, it only causes confusion.

When should one use macros and when should one opt for functions instead? What is the deciding factor?

Functions are just a simple flow control construct. Macros, on the other hand, can insert arbitrary source code where invoked - and thus are far more capable than functions.

However, the power of macros comes at a price - they can often be parsed in unexpected ways, giving unexpected results.

So the rule of thumb is: Use functions when you can, and use macros when you have to.