Maximal min() and max()
min() and max() for the kernel
Your editor's well-worn, first-edition copy of The C Programming Language introduces the preprocessor with this example:
#define max(A, B) ((A) > (B) ? (A) : (B))
The hazards that come with a macro like this, such as the double evaluation of the arguments, were pointed out in the text. Still, that did not prevent kernel developers from making use of it; as covered here in 2001, there were over 150 definitions of min() and max() matching the above pattern in the 2.4.8 kernel.
At that time, Linus Torvalds decided that a consolidation made sense; he added a single set of those macros meant to be used throughout the kernel. He also changed the interface, though, adding a type parameter describing how the comparison is to be performed — signed or unsigned integer, for example. The goal was to increase correctness, but the immediate effect was to break compilation throughout the kernel; the result was a classic linux-kernel flame war of the type that, fortunately, tends not to happen anymore.
Despite the complaints, the changes stuck — briefly. When the 2.4.9.8
release came about in February 2002, it included a change described as:
"make the three-argument (that everybody hates) 'min()' be 'min_t()',
and introduce a type-anal 'min()' that complains about arguments of
different types
". The max() and min() macros were
back to their old form, but the definition had changed; they now looked
like:
#define min(x,y) ({ \ const typeof(x) _x = (x); \ const typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x < _y ? _x : _y; })
Unsurprisingly, the complexity of these macros only grew from there as developers added more features for flexibility and type safety. Numerous variants have also been added for special cases. Recently, this series from David Laight, merged for the 6.7 kernel, made min() and max() work properly in numerous cases where the two arguments have different types. All seemed well after that, and nobody felt a compelling urge to change these macros for at least three development cycles.
Maximal expansion
But, then, Arnd Bergmann observed that the time required to compile the kernel had grown considerably in recent releases, and that the preprocessor had a lot to do with it; one file took a full 15 seconds just to get through the preprocessor stage. The problem came down to a single line of code in arch/x86/xen/setup.c:
extra_pages = min3(EXTRA_MEM_RATIO * min(max_pfn, PFN_DOWN(MAXMEM)), extra_pages, max_pages - max_pfn);
To see how this came about, it is worth looking at the 6.10 definitions of the min() and max() macros and their variants, all of which come from include/linux/minmax.h. To start with, min3() returns the minimum of three values; its implementation is straightforward enough:
#define min3(x, y, z) min((typeof(x))min(x, y), z)
That uses our old friend min(); indeed, it nests one min() call inside another. In 6.10, min() looks like this:
#define min(x, y) __careful_cmp(min, x, y)
The __careful_cmp() macro tries hard to perform a type-safe comparison while evaluating the arguments only once; it also endeavors to expand to a constant expression if its arguments are constant expressions. That leads to a certain amount of complexity, implemented this way (best read from bottom to top):
#define __cmp_op_min < #define __cmp_op_max > #define __cmp(op, x, y) ((x) __cmp_op_##op (y) ? (x) : (y)) #define __cmp_once_unique(op, type, x, y, ux, uy) \ ({ type ux = (x); type uy = (y); __cmp(op, ux, uy); }) #define __cmp_once(op, type, x, y) \ __cmp_once_unique(op, type, x, y, __UNIQUE_ID(x_), __UNIQUE_ID(y_)) #define __careful_cmp_once(op, x, y) ({ \ static_assert(__types_ok(x, y), \ #op "(" #x ", " #y ") signedness error, fix types or consider u" #op "() before " #op "_t()"); \ __cmp_once(op, __auto_type, x, y); }) #define __careful_cmp(op, x, y) \ __builtin_choose_expr(__is_constexpr((x) - (y)), \ __cmp(op, x, y), __careful_cmp_once(op, x, y))
Depending on the expressions passed in, this means that min3() can end up generating a fair amount of code. Even if one expects a large expansion, though, the actual amount may lead to significant eyebrow elevation: the single line of code shown above expands to 47MB of preprocessor output. Bergmann explained this result this way:
It nests min() multiple levels deep with the use of min3(), and each one expands its argument 20 times times now (up from 6 back in linux-6.6). This gets 8000 expansions for each of the arguments, plus a lot of extra bits with each expansion. PFN_DOWN(MAXMEM) contributes a bit to the initial size as well.
Kernel developers, as a rule, care deeply about efficiency; that is especially true when it comes to the time required to do a kernel build. So it is unsurprising that this problem attracted some attention once it came to light.
Minimizing the problem
Lorenzo Stoakes brought the issue to the linux-kernel mailing list, showing how the 6.7 changes had made compilation time worse. Laight posted a patch series one day later that attempted to mitigate the problem. That series improved compilation time, though not enough to completely make up for the build-time regressions seen. It also ended up provoking some warnings from the test bots, and some of the changes to the macros made some developers (including Bergmann) nervous; those macros have reached a level of subtlety that makes people reluctant to change them. Torvalds, too, was uncomfortable with some of the changes, but he also wondered if they were the right approach to take in the first place:
I do get the feeling that the problem came from us being much too clever with out min/max macros, and now this series is doubling down instead of saying "it wasn't really worth it".
He later suggested
simply reverting the 6.7 changes even though the previous code was
"stupid and limited and caused us to have to be more careful about types
than was strictly necessary
" but, as Stoakes pointed
out, a lot of code in the kernel has since come to depend on the new
functionality that those changes added. Reverting them now would not be a
straightforward task.
So Torvalds decided to take a bit of a different approach after observing that many of the worse expansion cases were, in the end, relatively simple constant expressions. Rather than try to fix the existing complex macros, he just added a couple more with a familiar look to them:
/* * Use these carefully: no type checking, and uses the arguments * multiple times. Use for obvious constants only. */ #define CONST_MIN(a,b) ((a)<(b)?(a):(b)) #define CONST_MAX(a,b) ((a)>(b)?(a):(b))
By the time these macros landed in the mainline they had naturally gained just a little complexity (and new names):
#define MIN_T(type,a,b) __cmp(min,(type)(a),(type)(b)) #define MAX_T(type,a,b) __cmp(max,(type)(a),(type)(b))
He converted a number of the worst expansion cases to use the new macros just prior to the 6.11-rc1 release, then merged a patch taking away the ability for min() and max() to work as part of a constant expression. That simplified the code somewhat at the cost of making the macros unsuitable for use in places where constants are needed, but the new macros can be used instead in such situations.
These changes will not entirely resolve the problem in cases where the
expressions are not constant, so chances are that more tweaks to the
regular min() and max() macros are in store. Meanwhile,
though, we have had a convincing demonstration of the sorts of pitfalls
that can accompany this sort of extensive use of the C preprocessor. It
can accomplish some magical-seeming effects, but spells of this nature
often have subtle and unpleasant side effects.
Index entries for this article | |
---|---|
Kernel | Build system |
Posted Aug 1, 2024 14:50 UTC (Thu)
by jhoblitt (subscriber, #77733)
[Link] (13 responses)
Posted Aug 1, 2024 15:52 UTC (Thu)
by clugstj (subscriber, #4020)
[Link] (12 responses)
Posted Aug 1, 2024 17:17 UTC (Thu)
by quotemstr (subscriber, #45331)
[Link] (4 responses)
Posted Aug 2, 2024 0:51 UTC (Fri)
by DanilaBerezin (guest, #168271)
[Link] (1 responses)
Posted Aug 5, 2024 20:23 UTC (Mon)
by cytochrome (subscriber, #58718)
[Link]
A new scientific truth does not triumph by convincing its opponents and making them see the light, but rather because its opponents eventually die and a new generation grows up that is familiar with it ...
Posted Aug 2, 2024 15:34 UTC (Fri)
by bmork (subscriber, #88411)
[Link]
I see I'm not the only one missing the good old flame wars :-)
Posted Aug 2, 2024 23:32 UTC (Fri)
by iteratedlateralus (guest, #102183)
[Link]
Posted Aug 1, 2024 17:20 UTC (Thu)
by jhoblitt (subscriber, #77733)
[Link] (2 responses)
Doesn't the kernel already allow C11?
Posted Aug 1, 2024 17:59 UTC (Thu)
by adobriyan (subscriber, #30858)
[Link]
Posted Aug 2, 2024 0:07 UTC (Fri)
by gerdesj (subscriber, #5446)
[Link]
I suspect you meant "oblivious" and yet I prefer "happily obvious" ... not too sure why. Perhaps its staring me in the face.
Posted Aug 2, 2024 1:00 UTC (Fri)
by Keith.S.Thompson (subscriber, #133709)
[Link] (2 responses)
The macro would have to list all the types you want the macros to work with, possibly with a "default:" to handle all types you haven't specified. But how would the code differ for different types?
You could catch type mismatches, like "min(1, 1.5)", but that would require nested generic selections on both arguments.
Personally, without having studied the kernel code that uses it, I would think that something simple like:
#define MIN(x, y) ((x) < (y) ? (x) : (y))
would be sufficient. The all-caps macro names emphasize that the arguments might be evaluated more than once and remind users to be careful. Any code that uses them could be audited (not on every build) to ensure it behaves sanely.
Quite possibly I'm missing something.
Posted Aug 2, 2024 16:30 UTC (Fri)
by bluss (guest, #47454)
[Link] (1 responses)
Posted Aug 4, 2024 15:40 UTC (Sun)
by fw (subscriber, #26023)
[Link]
Posted Aug 4, 2024 15:37 UTC (Sun)
by fw (subscriber, #26023)
[Link]
Posted Aug 1, 2024 17:14 UTC (Thu)
by quotemstr (subscriber, #45331)
[Link] (41 responses)
C++ would allow the "shape" of the kernel to be unchanged (nobody makes you use, e.g. std::vector!) while allowing the kernel to replace macro abominations like the DEFINE_TRACE, 8000-macro-expansion max(), etc. with mere template abominations. Like mathematical infinities, some abominations are more abominable than others.
C++'s template system _comprehensively_ solves the _entire class_ of problem this article discusses, and it does so in a way that's elegant, composable, comprehensible, and safe. There is literally no C program that cannot trivially be made a C++ program.
Modern dialects (e.g. with variadic templates) so comprehensively clobbers ANSI C on safety and expressiveness without giving up an iota of performance that one struggles to imagine reasons other than mere inertia keeping C++'s safe and powerful type system out of kernel developer hands.
Posted Aug 1, 2024 17:21 UTC (Thu)
by mb (subscriber, #50428)
[Link] (38 responses)
Posted Aug 1, 2024 17:23 UTC (Thu)
by quotemstr (subscriber, #45331)
[Link] (28 responses)
Does it? Why would it?
Posted Aug 1, 2024 17:30 UTC (Thu)
by mb (subscriber, #50428)
[Link] (27 responses)
Posted Aug 1, 2024 17:34 UTC (Thu)
by quotemstr (subscriber, #45331)
[Link] (18 responses)
Posted Aug 1, 2024 17:59 UTC (Thu)
by mussell (subscriber, #170320)
[Link] (17 responses)
A quick check on my system shows that the idiomatic C++ code
#include <iostream>
int main() {
takes about 9x as long to compile using G++ compared to your idiomatic C code in GCC (0.27s vs 0.03s). Even building the C code in G++ takes 0.07s which is twice as long as GCC and about the same time as clang (not clang++).
Posted Aug 1, 2024 18:05 UTC (Thu)
by quotemstr (subscriber, #45331)
[Link] (13 responses)
It's C++ code that also happens to be valid C code. Compiling it as C++ is just as fast as compiling it as C. This result refutes mb's claim that compiling C++ is necessarily slow. There are more nuanced points to be made, as you do, but mb is dead-to-rights wrong.
> Once you start using templates like std::cout (which is what every C++ tutorial uses), compilation suffers dramatically as template instantiation can create the equivalent of thousands of lines of code, all for it to be deleted by a later compiler pass.
And if you instantiate a C macro 8,000 times, performance suffers. Nobody is forcing you to use <iostream> or std::cout just because you're writing C++
> idiomatic C code in GCC (0.27s vs 0.03s)
The Linux kernel isn't "idiomatic" C. Why should it be "idiomatic" C++? If the kernel can avoid massive macro expansions, it can avoid massive template instantiation too. My point is that both macros and C++ metaprogramming generate code, but the latter is a safer and more concise way to get to the same endpoint.
There's nothing *inherent* in C++ that turns tight C programs into bloated Boost-Spirit-style masses of template expansions and specialization, no more so that C inherently turns programs into mazes of CPP macros.
C++ template is a better meta-programming system than C macros, hands down. Neither language makes metaprogramming mandatory.
Posted Aug 1, 2024 18:12 UTC (Thu)
by mb (subscriber, #50428)
[Link] (12 responses)
Not true.
If all you have is C code, why on earth do you want to compile it with a C++ compiler?
Posted Aug 1, 2024 18:36 UTC (Thu)
by quotemstr (subscriber, #45331)
[Link] (11 responses)
Posted Aug 1, 2024 18:47 UTC (Thu)
by mb (subscriber, #50428)
[Link] (7 responses)
That's why they abuse the C preprocessor to the max (hehe) and that's why C++ features will be used, if available.
Show me a single C++ project that builds as fast as a comparable C project.
Posted Aug 1, 2024 18:49 UTC (Thu)
by quotemstr (subscriber, #45331)
[Link] (4 responses)
Posted Aug 1, 2024 18:51 UTC (Thu)
by mb (subscriber, #50428)
[Link] (3 responses)
Posted Aug 2, 2024 19:53 UTC (Fri)
by walters (subscriber, #7396)
[Link] (2 responses)
And some of the major offenders seemed to end up being constant values:
https://lwn.net/ml/all/CAHk-=wjPr3b-=dshE6n3fM2Q0U3guT4re...
```
That could be `const PAGEBLOCK_ORDER: u32 = ...`...except yes, I don't think there's any way yet to do min/max() (i.e. in a generic fashion) yet in Rust...blocked on "const fn in traits" https://github.com/rust-lang/rfcs/pull/2632 I think? But one can trivially define a `const fn` for it and use it like:
```
const fn min_u32(a: u32, b: u32) -> u32 {
const BASIC_VAL: u32 = 20;
There may even be a crate for `const fn` variants like that although it may be getting somewhat "left-pad" territory.
(And maybe I am missing a clever way to use the more elegant, generic and Rust-native Ord trait https://doc.rust-lang.org/std/cmp/trait.Ord.html#method.min in const expressions).
Posted Aug 2, 2024 20:03 UTC (Fri)
by adobriyan (subscriber, #30858)
[Link]
Posted Aug 5, 2024 14:02 UTC (Mon)
by cencer (subscriber, #40823)
[Link]
Posted Aug 4, 2024 5:04 UTC (Sun)
by ssmith32 (subscriber, #72404)
[Link] (1 responses)
If you're making a claim about *all* C++ projects, then, if there exists one C++ project that contradicts your point, you are *wrong*.
So, you're wrong. QED.
You could try a little nuance and subtlety...
Posted Aug 4, 2024 8:20 UTC (Sun)
by mb (subscriber, #50428)
[Link]
Posted Aug 3, 2024 10:34 UTC (Sat)
by marcH (subscriber, #57642)
[Link] (2 responses)
and _that_ is the problem with C++. Which intermediate point do you allow in your project? No one can tell and endless debates ensue.
For instance, everyone agrees that "modern" C++ is much more memory safe than C[*]. But what exact "intermediate point" is "modern"? Which "old" and unsafe features are now forbidden and must be replaced in everyone's code base at great re-validation expense? No one knows; not even Stroustrup: https://thenewstack.io/bjarne-stroustrups-plan-for-bringi... By the time C++ experts have agreed on that, even Rust will be obsolete and replaced.
This is what happens when you have too many cooks.
[*] memory safety is not very important but it's a fashionable topic nowadays /s
Posted Aug 3, 2024 12:20 UTC (Sat)
by malmedal (subscriber, #56172)
[Link] (1 responses)
Posted Aug 3, 2024 16:18 UTC (Sat)
by marcH (subscriber, #57642)
[Link]
I don't know whether it will help with other C++ issues (e.g.; Perl-like, "write-only" possibilities) but having a memory safe C++ would be great for humankind. No, I'm not exaggerating: think about the gazillions of C++ lines the world depends on.
Besides being a great language in itself, I think Rust has created a lot of pressure for C++ to (literally) clean up its act. Competition is good! Discussions about languages tend to be passionate, especially on "asocial" media but it would be great for the real world to end up with TWO good alternatives.
You just don't want "too much" competition and too much fragmentation either, especially not with programming languages: there's no room for 10 different C++ variants - which is the problem for now. Funny for a language to be so old and so experimental and immature at the same time.
Posted Aug 2, 2024 21:40 UTC (Fri)
by magfr (subscriber, #16052)
[Link] (2 responses)
Posted Aug 3, 2024 2:39 UTC (Sat)
by NYKevin (subscriber, #129325)
[Link]
Posted Aug 3, 2024 9:47 UTC (Sat)
by excors (subscriber, #95769)
[Link]
If you're not using C++20/23, you should probably use {fmt} anyway - I think it's an almost entirely compatible superset of the new std::format/std::print APIs, and it only requires C++11, and it's better than iostreams in every way.
Posted Aug 1, 2024 18:02 UTC (Thu)
by wtarreau (subscriber, #51152)
[Link] (7 responses)
Posted Aug 1, 2024 18:17 UTC (Thu)
by adobriyan (subscriber, #30858)
[Link] (6 responses)
a) modules -- Microsoft claims importing _all_ of std is faster then _including_ just some headers (?) (<vector> ?)
b) gcc needs to support C99 initializers in their full glory:
c) tell clang devs that arithmetic on "void*" is fine, so both compilers can be supported
gcc has -Wno-pointer-arith and Universe didn't collapse.
Tell both groups that "void* - void*" is fine!
d) deal with RESF
Everything else is minor nuisance in comparison.
Except implicit casts from void* to T* which is non issue but undoing 25 years of removing efforts in 1 commit... Oh.
Posted Aug 1, 2024 23:53 UTC (Thu)
by NYKevin (subscriber, #129325)
[Link] (5 responses)
> On ARM, ARM64, and x64 machines, __thiscall is accepted and ignored by the compiler. That's because they use a register-based calling convention by default.
As far as I can tell, that works out to thiscall being usable on every platform *except* (modern) Windows, which is mildly amusing (and presumably not an issue for Linux, but IDK maybe it gives the WSL people a headache).
Posted Aug 2, 2024 0:57 UTC (Fri)
by DanilaBerezin (guest, #168271)
[Link] (4 responses)
WSL is an actual virtual machine. So AFAIK it shouldn't.
Posted Aug 2, 2024 9:58 UTC (Fri)
by aragilar (subscriber, #122569)
[Link]
Posted Aug 2, 2024 9:59 UTC (Fri)
by rschroev (subscriber, #4164)
[Link] (2 responses)
"WSL is an actual virtual machine" is probably mostly true: I think WSL 2 is used much more than WSL 1.
Posted Aug 2, 2024 10:35 UTC (Fri)
by WolfWings (subscriber, #56790)
[Link]
But also a shockingly convenient setup since you can still run Windows binaries inside the WSL environment as well since it's also all just a local virtualized CIFS mount into all the VM filesystems.
Posted Aug 2, 2024 20:49 UTC (Fri)
by Heretic_Blacksheep (guest, #169992)
[Link]
"WSL is an actual virtual machine," as far as most people refer to it is not wrong. Almost no one is referring to WSL1 these days and they'll usually remember to specify if they are.
Posted Aug 1, 2024 18:01 UTC (Thu)
by adobriyan (subscriber, #30858)
[Link] (8 responses)
Posted Aug 2, 2024 22:51 UTC (Fri)
by wahern (subscriber, #37304)
[Link] (7 responses)
The problematic macro mess does more than 2-argument std:min. The earlier version,
Disclaimer: I've not done much C++, but looking at the template interface, and testing some sample code, AFAICT std:min requires integers of the same type. Please correct me if I'm wrong. I would imagine any solution that permits safely and optimally comparing differently typed integers is going to be messy in most languages. If it's not messy--or at least verbose--it's not likely going to produce the most efficient machine code. It would be interesting to compare an equivalent C++ template solution in terms compiler memory and CPU cost. I wouldn't hazard a guess, and wouldn't be surprised by any particular result.
Posted Aug 3, 2024 10:02 UTC (Sat)
by roc (subscriber, #30627)
[Link] (4 responses)
Posted Aug 3, 2024 21:58 UTC (Sat)
by wahern (subscriber, #37304)
[Link]
Given that we're only dealing with integral types, and any type narrower than an int will be promoted to an int--reducing the set of possible permutations--one wonders whether some manual enumeration might help. Perhaps there are issues with qualified types and otherwise coercing the compiler to match arguments to a small enough set of _Generic cases? Maybe this is where C++ would shine, with a well defined set of template type matching rules, plus constexpr. (GCC provides constexpr for C code, but only in very recent versions, in anticipation of constexpr in C23.)
Posted Aug 5, 2024 10:13 UTC (Mon)
by kleptog (subscriber, #1183)
[Link] (2 responses)
Posted Aug 5, 2024 12:54 UTC (Mon)
by Wol (subscriber, #4433)
[Link] (1 responses)
Cheers,
Posted Aug 5, 2024 16:25 UTC (Mon)
by kleptog (subscriber, #1183)
[Link]
Posted Aug 3, 2024 23:13 UTC (Sat)
by NYKevin (subscriber, #129325)
[Link] (1 responses)
#include <algorithm>
char find_min(unsigned char a, unsigned char b){
int main(){
To be fair, that's not really something that std::min() is capable of fixing, nor is it specific to C++ (C will do exactly the same thing, given the opportunity). I personally see this as one of C and C++'s cardinal sins, but it's not a reason to prefer one over the other.
(No this is not UB, according to https://en.cppreference.com/w/cpp/language/implicit_conve... it is implementation-defined before C++20 and wrapping after.)
Posted Aug 20, 2024 7:29 UTC (Tue)
by daenzer (subscriber, #7050)
[Link]
Posted Aug 2, 2024 23:57 UTC (Fri)
by iteratedlateralus (guest, #102183)
[Link] (1 responses)
I think the major hurdle there is to have Linus accept C++ as a language he wants to use. He is not a fan of the language.
Posted Aug 3, 2024 16:00 UTC (Sat)
by Sesse (subscriber, #53779)
[Link]
(I'm not saying the answer is right, nor wrong)
Posted Aug 2, 2024 23:31 UTC (Fri)
by iteratedlateralus (guest, #102183)
[Link]
Posted Aug 9, 2024 16:32 UTC (Fri)
by mbp (subscriber, #2737)
[Link]
compiler magic?
compiler magic?
compiler magic?
compiler magic?
compiler magic?
compiler magic?
compiler magic?
compiler magic?
compiler magic?
compiler magic?
compiler magic?
#define MAX(x, y) ((x) > (y) ? (x) : (y))
compiler magic?
compiler magic?
compiler magic?
C++
C++
With C++ the build performance goes down the drain.
C++
C++
Uh, no?
A myth doesn't become truth just because someone repeats it emphatically enough.
C++
$ time for ((i=0; i < 100; ++i)); do gcc -O2 foo.c; done
real 0m6.669s
user 0m4.412s
sys 0m2.237s
~
$ time for ((i=0; i < 100; ++i)); do gcc -O2 -x c++ foo.c; done
real 0m6.995s
user 0m4.677s
sys 0m2.302s
$ cat foo.c
#include <stdio.h>
int
main()
{
printf("hello world\n");
return 0;
}
C++
std::cout << "Hello, world!" << std::endl;
return 0;
}
C++
C++
Of course you would have C++ code (templates) in there.
That was *your* point. You want to replace C macros with C++ templates.
C++
C++
People use the features that are available.
That is the whole point of compiling with a C++ compiler.
C++
C++
C++
#define pageblock_order min_t(unsigned int, HUGETLB_PAGE_ORDER,
MAX_PAGE_ORDER)
```
mod someotherlib {
pub(crate) const FOO: u32 = 42;
}
if a < b {
a
} else {
b
}
}
const SOMEVAL: u32 = min_u32(someotherlib::FOO, BASIC_VAL);
```
C++
C++
C++
C++
A completely made up nonsense project that only exists to prove that I am wrong builds as fast as C.
Any other real world projects builds slowly.
C++
C++
C++
That is not the recommended way to write Hello World! in C++ any more.
The new fancy way as of C++23 is
C++
#include <print>
int main()
{
std::println("Hello World!");
}
C++
C++
C++
C++
this solves (in theory) compile time degradation (which exists, Linux++ compiles noticeably slower than Linux)
forcing devs to place initalizers in order is downgrade in kernel programming experience.
Clang can do it. See : https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113124
does Rust support extern "C++" and what else is necessary? I don't know.
Presumably interop goes via extern "C" but (!) all of kernel becomes extern "C++" and they want to interop potentially with _all_ kernel subsystems!
C++
C++
C++
WSL
WSL
WSL
C++
C++
is the std:min equivalent, and should be at least as easy on the compiler. (That said, while C23 has typeof, statement expressions are still non-standard.) A trivial min3 could be written using the same typeof trick. The newer min3 and min rely on __careful_cmp, which (among other features) is apparently intended to safely permit comparison of integers of different type, similar to the classic MIN macro, but without the signed -> unsigned conversion pitfall which can happen with integral promotion at the highest integral rank.
#define min(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
C++
C++
C++
C++
Wol
C++
C++
#include <iostream>
return std::min(a, b);
}
std::cout << int(find_min(255, 254)) << std::endl;
}
C++
C++
C++
Classification as...
What a great story