@uecker@mastodon.social cover

Computer Science and Biomedical Engineering
Graz University of Technology

This profile is from a federated server and may be incomplete. View on remote instance

@uecker@mastodon.social avatar uecker , to random
ALT
@vitaut@mastodon.social avatar vitaut , to random

So one side effect of C being a mess is that bool and char constants are formatted as integers by default. You have to either cast them to the correct type or use an explicit format specifier which is a bit annoying.

uecker ,
@uecker@mastodon.social avatar

@floooh @joe @vitaut @thradams You need to pass -std=c23

@thephd@pony.social avatar thephd , to random

Well, after having to write the following god-awful <ztd/idk/detail/cstring_fix.hpp> header to get this to work on MacOS using GCC 15 on literally-any-recent-MacOS-header:

#include <ztd/idk/version.hpp><br></br><br></br>#if ZTD_IS_ON(ZTD_PLATFORM_MAC_OS) && ZTD_IS_ON(ZTD_COMPILER_GCC)<br></br>#if __GNUC__ <= 15<br></br>typedef size_t rsize_t;<br></br>#endif<br></br>#endif<br></br>#include <cstring><br></br>

I'm happy to report I have some preliminary numbers on how good/bad certain styles of closure styles in C are. And, well.... turns out that despite maybe having some design equivalencies, All Things Are Not Created Equal.

I'll have to write a blog post about this after running more iterations and getting more concrete numbers, maybe for both ARM64 and AMD64 hardware to do a cross-compare.

The second numbers are using Clang, and because both GCC and Clang hate each other in this respect, both don't have the other's extension. That means it's not a perfect apples-apples comparison for the two numbers here, given that GCC handles things differently from Clang, so that will also have to be elaborated about in the forthcoming explanation....

I'm not happy about how GNU Nested Functions are being tested; the benchmarking software is in C++, and GCC doesn't allow nested functions in C++. So I have to extern "C" call into regular GCC C code. It's compiled in the same executable so obviously inlining could save it but I'm not exactly happy since all the others can have their definitions and usage perfectly in-line. I might need to tweak the benchmark code more to make sure it's more fair in this regard.

2025-12-02T03:27:52+01:00 Running /Volumes/Shareland/Sync/Cross/ztd/idk/.cmake/build-Darwin/bin/Release/ztd.idk.benchmarks.closures Run on (8 X 24 MHz CPU s) CPU Caches: L1 Data 64 KiB L1 Instruction 128 KiB L2 Unified 4096 KiB (x8) Load Average: 6.20, 5.18, 4.60 ------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------- apple_blocks 7728 ns 7685 ns 87211 custom_callable_cxx 4271 ns 4247 ns 165077 lambda 2460 ns 2444 ns 287809 lambda_function_ref 4535 ns 4511 ns 154730 lambda_rosetta 3988353 ns 3964746 ns 177 lambda_std_function 34744 ns 34625 ns 20270 noop 0.000 ns 0.000 ns 1000000000000 normal_functions_rosetta 6352 ns 6322 ns 110740

ALT
uecker ,
@uecker@mastodon.social avatar

@thephd Haha, ALGOL 60 is nicest and then it went downhill.

@thephd@pony.social avatar thephd , to random
    static int a(int k, fn_t ^ x1, fn_t ^ x2, fn_t ^ x3, fn_t ^ x4, fn_t ^ x5) {<br></br>        fn_t ^ b = ^(void) { return a(k - 1, b, x1, x2, x3, x4); };<br></br>        // clang-format off<br></br>        return k <= 0 ?<br></br>            x4() + x5()<br></br>            :<br></br>            b();<br></br>        // clang-format on<br></br>    }<br></br>

If the bug in this code is what I think it is, I'm gonna be even more mad that __self_func was rejected by WG14 lmao.

uecker ,
@uecker@mastodon.social avatar

@thephd It just works with GCC's nested functions, which is the correct model. (yes, not the trampolines)

uecker ,
@uecker@mastodon.social avatar

@rjmccall @thephd Indeed, just give it a name - like a real nested function.

uecker ,
@uecker@mastodon.social avatar

@thephd @rjmccall Based on my experience, I have no strong need for lambda syntax, but I wouldn't mind having it. Ada is a language which does not have it or Pascal, etc.

@Mattias_G@mastodon.gamedev.place avatar Mattias_G , to random

I am excited to share this new single-header C library I have been working on for a while now: vecmath.h, a comprehensive vector/matrix math library for graphics/games/3d.
It allows you to write vector math code in C that looks like this:

uecker ,
@uecker@mastodon.social avatar

@floooh @thephd @Mattias_G Huh? Most math code definitely uses arbitrary sized matrices. Only game development is where vec3/4 is interesting.

uecker ,
@uecker@mastodon.social avatar

@floooh @thephd @Mattias_G I was just wondering a bit about your definition of "real world" ...

uecker ,
@uecker@mastodon.social avatar

@floooh @thephd @Mattias_G The vector length discussions always reminds me about the old joke "How do you visualize 4-dimensional space?" "That's easy. Just imagine N-dimensional space and set N=4."

uecker ,
@uecker@mastodon.social avatar

@floooh @thephd @Mattias_G Far off the mark. I build real-time numerical code in C for medical imaging.

@thephd@pony.social avatar thephd , to random

A bit long, but please read the introduction to this (not quite yet finished) paper: https://thephd.dev/_vendor/future_cxx/papers/C%20-%20Functional%20Functions.html

Feedback welcome. I still need to write a section on how it works in ISO C terms, but this is mostly to cover the existing situation.

uecker ,
@uecker@mastodon.social avatar

@thephd Note that this quote from me iis out of context, my paper to propose adding nested function to ISO C from 2021 clearly argues for a solution that does not use executable stack or tampolines and argues for a new type.

uecker ,
@uecker@mastodon.social avatar

@rjmccall @thephd And maybe not misrepresent my position with out of context quotes. My first paper discusses all implementation options already and does not propose executable stack or trampolines.

uecker ,
@uecker@mastodon.social avatar

@thephd @rjmccall Sorry, ,y paper directly mentions the ABI problem explicitly: "changing the pointer type would be an ABI breaking change" directly after discussing descriptors. For how this works with existing code, you need to look at the wide pointer proposal that has examples. Also note that I have toy implementation of all this, so I know it works with existing code and without trampolines or executable stack.. You can stop the bullshit now.

uecker ,
@uecker@mastodon.social avatar

@thephd @rjmccall Also note that my proposal captures the existing practice of what GCC and clang already implement (the wider pointer API would go a bit beyond it). What my proposal does not is making nested functions copyable, but it would perfectly compatible to adding this. But I am still not sure this is a good idea without automatic life times. if you believe nested functions are unsafe because their life time is limited, having the user do explicit copying certainly does not fix this.

uecker ,
@uecker@mastodon.social avatar

@thephd @rjmccall I guess a have to write a new paper... that combines nested functions / wide pointer + optional copying so that this is easier to see how it works. What I would hate to see is that we cargo-cult a C++ solution into C that does not properly work because we do not have smart pointers and all that. @rmjccall makes this argument above and I made the exact same argument against C++-style lambdas when this was proposed for C23 (and was heavily attacked in WG14 by the C++ fans).

uecker ,
@uecker@mastodon.social avatar

@thephd @rjmccall What I said the C++ design with an anonymous opaque type does not make sense for C because copying values you can not otherwise access does not work (how do you free it? How do you deep copies?). You need to be able to write your own copy structure that can access all the value. A solution is to make the captures values visible as a structure that can be accessed. This can be done and it can done as for nested functions. And this has been discussed before.

uecker ,
@uecker@mastodon.social avatar

@thephd @rjmccall The question is whether this is a big enough improvement over explicitly putting things into a structure yourself.

uecker ,
@uecker@mastodon.social avatar

@thephd @rjmccall Anyway, if we had just added nested functions and wide pointers, we could now just add this on top now. But we can not even make the first step, because somebody always shouts "no GNU nested function because of executable stack" despite nobody even having proposed this. It simply is a red herring.

uecker ,
@uecker@mastodon.social avatar

@thephd @rjmccall The funny thing is that using these stuff for a very long time, and you are coming in with very strong opinions telling me how all this unsafe, a footgun, so bad for users.... Sorry, I can't take you seriously.

@nixCraft@mastodon.social avatar nixCraft , to random

Heads up: The next version, Ubuntu 25.10 "Questing Quokka," is making a big change. They are getting rid of the Xorg system for the Ubuntu desktop. Starting with this release the “Ubuntu” session in GDM will run exclusively on Wayland. https://discourse.ubuntu.com/t/ubuntu-25-10-drops-support-for-gnome-on-xorg/62538

uecker ,
@uecker@mastodon.social avatar

@nixCraft Luckily I use neither Gnome nor Ubuntu.

@thephd@pony.social avatar thephd , to random

Therefore, we define a new concept "representation equivalence" so that we can support the notion of conversion not only between _Any_func* and void*, but void* and any function pointer type. This also further helps us because we can specify properties beyond what is currently blessed by the C standard, and make allowances for the popular platforms which form the overwhelming super majority of platforms that developers work on, even in C.

Let's start moving towards a better world.

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3556.htm

uecker ,
@uecker@mastodon.social avatar

@thephd Have you considered making this a library type?

uecker ,
@uecker@mastodon.social avatar

@thephd One could have a library feature that converts any function pointer type to it and back?

uecker ,
@uecker@mastodon.social avatar

@erisceleste @thephd This is only true if you want conversions to work, but those could also be provided by the library.

uecker ,
@uecker@mastodon.social avatar

@thephd @erisceleste Maybe, or a macro

uecker ,
@uecker@mastodon.social avatar

@erisceleste @thephd I am actually fine with implicit conversions. So I think if you wanted to make this nice, then the design makes a lot of sense. But then, it adds an awful lot of stuff for an obscure and inherently unsafe feature...

uecker ,
@uecker@mastodon.social avatar

@thephd @erisceleste Well, on POSIX void does the job. So it is only needed on non-POSIX platforms where pointers to functions have a larger size. And then also only if you need to avoid another indirection. And it is not type safe to convert a type to a generic type and back.

uecker ,
@uecker@mastodon.social avatar

@thephd @erisceleste Oh, I am not saying that converting to void is better.

@thephd@pony.social avatar thephd , to random

HEARTBREAKING: you tried to break out of a for loop inside of a switch statement in dumbass languages like C and C++. Your code fails and everyone laughs at you.

uecker ,
@uecker@mastodon.social avatar

@thephd
This works now:
https://godbolt.org/z/b45bKoT51

(although, to be honest, I find goto clearer)

@fanf@mendeddrum.org avatar fanf , to random

hmm, a lacuna?

the C reference manual in K&R (both editions) clearly specifies the type of a string literal, but the standards just say the literal initializes a static array without specifying exactly how the array relates to the type and value of the expression

(though when it describes array -> pointer coercion the standard obliquely implies string literals can have an array type, but it doesn’t say their value can’t be a pointer to the first element)
https://mastodon.gamedev.place/@amonakov/114376731621701333

uecker ,
@uecker@mastodon.social avatar

@fanf Maybe read the ISO C standard?

@thephd@pony.social avatar thephd , to random

I am burdened by the immense crossroads C is at right now with respect to arrays. Not just for spans (cheap array views), but for general purpose slicing; there's a lot riding on the next 3 years.

And I am unsure how to communicate that vision to the Committee...

uecker ,
@uecker@mastodon.social avatar

@Doomed_Daniel @thephd Arrays have real length in C. You can not pass them by value though (or only wrapped in a struct).

uecker ,
@uecker@mastodon.social avatar

@Doomed_Daniel @thephd I agree, We are working on it. We already have an operator, i.e. _Countof. for the length and a series of other improvements. But you can already write this and it is safe: https://godbolt.org/z/YqnKTT6ds

@uecker@mastodon.social avatar uecker , to random
@thephd@pony.social avatar thephd , to random

Clearly I haven't gotten enough of my 🍑 kicked by the standards process. So, here we go, doing Big Work all over again. The first of many larger changes for C that should've been done 25 years ago, since the days of __try/__finally.

Let's get it done. ⤵

The Defer Technical Specification: It Is Time | Björkus Dorkus | The Pasture | https://thephd.dev/c2y-the-defer-technical-specification-its-time-go-go-go

uecker ,
@uecker@mastodon.social avatar

@thephd Excellent! Btw: I think it is just "reverse order" (or "reverse order of appearance in the source code" if you want to be more precise) and not "lexicographic order" because it is not ordered by the spelling of its name (there is no name)

@thephd@pony.social avatar thephd , to random
uecker ,
@uecker@mastodon.social avatar

@thephd Why? Seems nicely consistent to me. This, on the other hand, is UB with compiler divergence:
https://godbolt.org/z/x9eEE1qPa

uecker ,
@uecker@mastodon.social avatar

@thephd It is indeed problematic to have VLA in structs (also as last element seems ok to me). But I like to point out that many other languages, e.g. Ada, also have such features.

uecker ,
@uecker@mastodon.social avatar

@thephd I am confused. It has the same semantics as VLA. The size is not passed with the struct but frozen as part of the type and this is the same as with VLAs.

uecker ,
@uecker@mastodon.social avatar

@thephd But this only because you define a local struct and then the nested functions can use its type (which encodes the value). You can do th e same with VLAs by using a typedef in main and then use this typedef name in the nested functions. The semantics are the same. (and I agree it sucks that no compiler checks that the sizes remain consistent - but I am working on this for GCC. this is completely fixable).

uecker ,
@uecker@mastodon.social avatar

@thephd
This would be the corresponding example using a VLA type:
https://godbolt.org/z/4YbsbEd1K

uecker ,
@uecker@mastodon.social avatar

@thephd And this would be the struct example where you can lie about the size: https://godbolt.org/z/f58n8c5Gn (and GCC unfortunately will not check)

uecker ,
@uecker@mastodon.social avatar

@thephd Still, the part which works and which is cool is this:
https://godbolt.org/z/bKTze5YMd
(edit: use bounds-strict)

uecker ,
@uecker@mastodon.social avatar

@thephd Again, the fix is very simple: Have compilers add checks either at run-time and/or compile-time, but because bounds checks are run-time anyway, there is no harm in having this at run-time. I think it integrates very well and except for the missing checks, it is exactly how it works in other languages.

@luna@pony.social avatar luna , to random

memory unsafety may cause a lot of software crashes, but it also helps a lot of people jailbreak their game consoles, so, it;s impossible to say if its bad or not,

uecker ,
@uecker@mastodon.social avatar

@aeva @luna I think memory safety is still generally a very good thing, but I also found the memory safety push a bit one-sided. Small companies do not get hacked because of some memory safety related exploit in the Linux kernel, but because a php module in the webserver was not updated and all servers use use ssh with root password auth activated and all have the same password, etc.

@thephd@pony.social avatar thephd , (edited ) to random
// from a hypothetical stdlib where things are not bad<br></br>typedef void(stdc_qsort_sorter_t)(<br></br>     const void*, const void*<br></br>);<br></br>void stdc_qsort(void* ptr,<br></br>     size_t count, size_t size,<br></br>     stdc_qsort_sorter_t* comp, void* comp_userdata);<br></br><br></br>#include <string.h><br></br><br></br>int main (int argc, char* argv[]) {<br></br>     const char* sorted_args[argc] = {};<br></br>     memcpy(sorted_args + 0, argv + 0, sizeof(sorted_args));<br></br><br></br>     int sorter(const void pa, const void pb)<br></br>     _Capture(argc, &argv) {<br></br>          const char a = (const char)pa;<br></br>          const char b = (const char)pb;<br></br>          if (a == nullptr && b == nullptr) return 0;<br></br>          if (a == nullptr) return -1;<br></br>          if (b == nullptr) return 1;<br></br>          size_t ai = 0;<br></br>          size_t bi = 0;<br></br>          for (; ai < argc; ++ai) if (a == argv[ai]) break;<br></br>          for (; bi < argc; ++bi) if (b == argv[bi]) break;<br></br>          return (argc - ai) - (argc - bi);<br></br>     }<br></br><br></br>     int sorter_jump(const void8 pa, const void8 pb, void* ud)<br></br>     _Capture() {<br></br>          typeof(sorter)* p_nested = ud;<br></br>          return (*p_nested)(pa, pb);<br></br>     }<br></br><br></br>     stdc_qsort(sorted_args,<br></br>          _Lengthof(sorted_args), sizeof(*sorted_args),<br></br>          sorter_jump, &sorter);<br></br>     // sorted_args is now sorted by distance from the end.<br></br>     // or whatever else someone wants, i guess<br></br>     return 0;<br></br>}<br></br><br></br>

A decent enough start...

uecker ,
@uecker@mastodon.social avatar

@thephd @DanielaKEngert This is partially the reason why Apple's blocks stuff has the reference counting under the hood.

uecker ,
@uecker@mastodon.social avatar

@thephd @DanielaKEngert If you have a wide pointer, then you do not need a unique type or typeof exercise in the first place. This is only useful if you want to be able to copy the closure around by value. Don't get me wrong, this is super interesting, because it could then escape the life time of the enclosing function. But it also almost useless in C without a practical way to manage the life time of objects the captured pointers point to.

uecker ,
@uecker@mastodon.social avatar

@thephd @DanielaKEngert So maybe in C we could explicitly create a structure that has a function pointer + named members for all captured variables. With such an explicit type a user could then add explicit destructors and deep copy functions as usual in C. This might even be compatible with C++. (We would still need the implicit conversion to a type-erased wide pointer that plays the role of std::function.)

uecker ,
@uecker@mastodon.social avatar

@thephd @DanielaKEngert The solution we envisioned with the wide pointer propsosal is that you define the nested function with the right type for the traditional API, including the void* and then you either "upgrade" by calling itself correctly, or by having another syntax that tells the compiler that the explicit void pointer has the static chain (instead of the static chain register)

uecker ,
@uecker@mastodon.social avatar

@thephd @erisceleste @DanielaKEngert blocks would not be suitable for a freestanding implementation. The problem with "just memcpy" is that you can not copy anything non-trivial or free pointer later, this is a severe limitation.

uecker ,
@uecker@mastodon.social avatar

@thephd @DanielaKEngert The NESTED_CALL only works on x86 but this is only if you need to avoid the executable stack on old GCC (I had this only for WSL 1). Nowadays, you can also use a heap implementation on GCC for the trampoline. But in any case, I would use the syntax from GCC but then use a wide type as blocks does on Clang and then you have the best of both worlds. No executable stack needed anywhere. For legacy code, I would upgrade the function pointer.