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.
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:
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.
@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.
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:
@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."
@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.
@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.
@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.
@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.
@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).
@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.
@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.
@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.
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.
@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...
@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.
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.
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
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...
@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
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.
@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 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.
@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.
@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).
@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.
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,
@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
@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.
@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.)
@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)
@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.
@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.