4 ms·
The only reason I can imagine for this behaviour is that the compiler/standard writers did not want it. Maybe C just shouldn't include generics. Especially not
by constantcrying 3y ago
The only reason I can imagine for this behaviour is that the compiler/standard writers did not want it.
Maybe C just shouldn't include generics. Especially not as part of the macro layer.
- mananaysiempre 3y agoThe macros in tgmath and more recently stdbit show why those could be necessary. If you have a set of functions for addition with overflow detection, say add_overflow{i,l,ll}, and you have a pair of ptrdiff_t’s or int32_t’s or whatnot that you know are standard integer types, and you want to use the appropriate add_overflow* function, can you do it? With _Generic you can. Without it I think you’re stuck providing separate functions for every integer typedef in the standard library and then requiring all library authors to do the same for both integer typedefs and integer-accepting functions that they define. (_Generic is not part of the macro level, that’s why the semantic-checking issues discussed in the article even arise. The C preprocessor can still be implemented as a separate binary that doesn’t understand C itself, even in C23.)
- maldev 3y agoYou make it sound alot more complicated. Ignoring the library functions or stuff you can include from safe coding standard headers. You just do something like. BOOL AddOverflowUnsigned(BYTE* a, BYTE* b, INT32 sizea, INT32 sizeb) { BOOL IsMsb = PlatformIsMSB(); BOOL IsLsb = PlatformIsLSB(); //log error if(!IsMsb && !IsLsb) return FALSE; //Early out for overflow. Assuming if(sizea == sizeb && PlatformIsMSB()) { BOOL AMsb = (a >> ((sizea * PlatformByteSize()) - 1)) & 1; BOOL BMsb = (b >> ((sizeb * PlatformByteSize()) - 1)) & 1; if(AMSB && BMsb) { //We overflow, early out. return FALSE; } else if(sizea == sizeb && PlatformIsLSB()) //Code here. //We add using bitwise operators so we can do it on any size. //SUM = A XOR B XOR CARRY, return in A //Impliment algo here. return TRUE; } int main() { UINT64 a = 69; UINT64 b = 420; if(sizeof(a) ! return AddOverflowUnsinged(&a, &b, sizeof(a), sizeof(b)); } But they have this in the STD now and also all the secure coding libs have this in it's header as a basic function. There's also the easier implementation of just adding a check to see if it will overflow, rather than add in the function. You also only have a few you can hardcode if you don't do it generic, since all will go back to the primitives of uint8, uint16 ....
- mananaysiempre 3y agoThis is OK as a fallback, even if it's not at all strictly compliant (the standard still allows PDP or Honeywell endian and padding bits wherever). As the main option it's extremely silly, though, when the entirety of the function on e.g. x86-64 could be (using the GCC signature but MS calling convention and syntax for according to your apparent preference) ; bool add_overflowll(long long x, long long y, long long *result) add_overflowll PROC add rcx, rdx seto eax mov qword ptr [r8], rcx ret add_overflowll ENDP for a total of no loops, no conditional branches, and three instructions with no branches at all when inlined (not shown because I don't remember the MS inline assembly syntax). Why check beforehand when the CPU does the check for you on every arithmetic operation and all you need to do is to ask it for the result? (Unless your CPU is RISC-V because RISC-V.) And of course the newfangled standard functions are size-dependent (more like mine than yours), so either you still need _Generic or essentially equivalent compiler-specific magic, or you have to introduce an ABI dependency on which integer type your ptrdiff_t or ssize_t or off_t or whatever typedef you got from a random place in a library or OS header actually ends up being.
- maldev 3y agoThat relies on architecture things that arn't in C. and that's why I say "ignoring the library functions or stuff you can include from safe coding standard headers. ". Of course you can do an intrinsic or whatever, and that's better obviously. But I wanted to show how easy it is to write a platform independent function to do this from raw C.
- Joker_vD 3y agoFirst of all, you forgot to return the actual sum from the function. Second, original add_overflow-style functions support arbitrary expressions as input arguments, not just pointers to l-values. And third, there is no way all this stuff is going to get optimized down to inlined add rdi, rsi setc eax mov [rdx], rdi
- maldev 3y agoReturn value is returned in a, it returns true or false on whether overflow happened. And you're assuming that you're on x86_64. You can't do that in C. C is platform independent. Which is why I said you can use one of the functions or intrinsics, and writing it yourself is bad, but still easy.
- quelsolaar 3y agoAs a member of the WG14 I can tell you that _Generic does cause a fair bit of issues and complications in the language, and as a user of C i think _Generic is bad, because it mostly useful for confusing users about what code does so, Yes C would be better off without _Generic. Please don't use it.
- sigsev_251 3y ago_Generic allows for more complex assertions in static_assert which is really useful for correctness (For example, your embedded chip vendor offers an API where uint8_t is used to transfer bytes using a protocol, so you have to assert that uint8_t is either char or unsigned char in order to prove that there will be no strict aliasing violation during pointer casting). Also, it's useful in order to implement the lengthof macro in a safe way that will not allow pointers to scalars or nullptr to be passed, but will only allow arrays, pointers to arrays and VMTs. There are even more uses of _Generic (if constexpr, compile time pattern matching, type traits, type safe formatting, macrowrappers to APIs that accept void*) that people have been using to emulate stronger typing and reject violations of the type system at compile time, I don't see how that's bad.
- quelsolaar 3y agoThere are always uses for every feature, thats how you end up with C++. The question is if it is worth the implementation burden, the complexity, making the language harder to learn and read, and the potential intentional and unintentional abuse. IMO it is clearly not.
- deleted 3y ago[deleted]
- j16sdiz 3y agoThis is bad for non-compiler tools. Without it, we can parse the preprocessed source code. With the enhancement proposed, we need to do half the compiler's work. In tools like IDE, we want quick (sub millisecond) feedback for most edits
- ActorNightly 3y agoGenerics are fine. Its useful to have some introspection into the data that compiler has. Thats the whole point of macros in the first place. The behavior with _Generic is basically emergent behavior from implementation of macro processors. Macro replacements occur prior to actual compilation, so no compiler context exists for x, as such all code paths must be valid. Its much easier to require the programmer typecast x in the replacement value then start trying to shoehorn the compiler context into macro processors.
- Dylan16807 3y ago> Macro replacements occur prior to actual compilation, so no compiler context exists for x, as such all code paths must be valid. How is it throwing an error like "invalid type argument of ‘->’" if there's no compiler context? And why does an error like that get in the way of macro replacements? (That might be the wrong message but the article says you at least get an equivalent.)
- ActorNightly 3y agoI mean in the sense of expanding the macro. It doesn't look at the type of operand at the macro expansion step, it simply replaces it with the _Generic() function call, which contains all the expressions, and each expression is parsed, and thus must be valid.
- Dylan16807 3y ago> It doesn't look at the type > each expression is parsed, and thus must be valid. There's multiple kinds of "valid". The code has valid syntax but invalid types. The preprocessor doesn't understand types, so it can't know the code has invalid types, right? So this error is not happening in the preprocessor, right? Therefore changing this behavior would not require preprocessor changes/shoehorning. This error is not a consequence of how the preprocessor works.
- teo_zero 3y ago_Generic is not part of the macro level.