13 ms·
Porting to GCC 14: C language issues
- actionfromafar 3y agoI like how delightfully stable GCC and C is when things from C99 is mandatory only now, a quarter of a century later.
- haolez 3y agoDidn't some parts of C99 get rolled back on C11 and other newer ones?
- fl0ki 3y agoVariable Length Arrays were mandatory in C99, optional in C11, and are mandatory again in C23. I'm curious how these dynamics play out in less popular compilers. If a compiler implemented VLAs in C99, they almost certainly still have that feature for backwards compatibility even after they support C11. Is there any compiler which appeared on the scene between C11 and C23, and during that window, chose not to support VLAs and thus C99? It's not like C11 itself was very widely adopted, precisely because of the long implementation and industry rollout windows.
- Joker_vD 3y agoUh, are they mandatory again? For these reasons, we propose to make variably-modified types mandatory in C23. VLAs with automatic storage duration remain an optional language feature due to their higher implementation overhead and security concerns on some implementations (i.e. when allocated on the stack and not using stack probing). from N2778 (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2778.pdf https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2778.pdf)
- fractalb 3y agoThanks for the link. As I understood it, it's just the support for the syntax that is mandatory. > Variable length arrays with automatic storage duration are a conditional feature that implementations need not support
- LegionMammal978 3y agoThe syntax and also the semantics. For instance, you can take the sizeof() a variably-modified type, or the offsetof() one of its fields, and the compiler has to do all the layout calculations implied by the type declaration at runtime. These features are partially what motivated the mandatory support. The only part that is still optional is using such types by value as stack variables (i.e., variables with automatic storage duration).
- actionfromafar 3y agoHow do you create a variable array on the heap? confused
- klodolph 3y agoWith `malloc`, and converting the pointer to the correct type. void function(int n) { int (*arr)[n] = malloc(sizeof(*arr)); }
- actionfromafar 3y agowow, that's wild
- uecker 3y agoThat is exactly how you create any other type of object on the heap.
- actionfromafar 3y ago
- fl0ki 3y agoI'm not tuned in to the nuances here but I note that whoever felt they understood it well enough to update Wikipedia summarized this as "The C23 standard makes VLA types mandatory again. Only creation of VLA objects with automatic storage duration is optional." https://en.wikipedia.org/wiki/Variable-length_array#C99 https://en.wikipedia.org/wiki/Variable-length_array#C99 If that phrasing isn't accurate I'm sure they'd appreciate an edit.
- fractalb 3y agoVLA support is mandatory in C23? I'd like to know the rationale behind this decision. Can you provide any references? Thanks.
- veltas 3y agoIf I understand right, this is mandatory: void example(int n) { printf("%zu", sizeof(int[n])); } void example2(int n, int (*a)[n]) { } This is optional: void example3(int n) { int x[n]; }
- garaetjjte 3y agoPortable code couldn't use VLAs anyway, it was never supported in MSVC.
- uecker 3y agoVLAs were optional in C11 and compilers that supported them in C99 also supported them in C11. The only important compiler not supporting VLAs is MSVC, but this compiler also did not support other features from C99. People using MSVC were stuck using an obsolete version of C for a long time. Recently this changed and MSVC started to support C11 and C17 - skipping C99.
- fl0ki 3y agoAnd still not even mandatory if you use -fpermissive or -std=c89. I don't envy how much tech debt they have to deal with, while still trying to make it possible for new code to have a more modern experience. For all the excitement around new languages, we're still going to have a lot of C code for a long time, and difficult work like this should be appreciated.
- estebank 3y agoI can't help but wonder how many bugs could have been detected and fixed a quarter of a century earlier if those lints had been adopted more diligently by the C ecosystem.
- loeg 3y agoMy perception is that more and better warnings, and common use of -Wall, came into vogue around the time of GCC 4, in the mid 2000s. It would have been pretty abrupt to immediately enforce C99 in 1999-2000.
- estebank 3y agoYet in 2022 this change from warn by default to error by default was consequential enough for Fedora: >> So what is the current status? How many packages are going to be affected by this change? How are we going to track the progress? > I see an unaudited rebuild failure rate of about 10% for rebuilds of source packages that produce arch-full binary packages. This number does not include packages which fail to build in rawhide without the compiler change. It includes packages which configure checks for something that we really do not support (like setproctitle or strlcpy). After the first pass completes, I'll have to do a second pass with the expected-to-be-undeclared functions gathered from the first pass filtered out. That should give us better numbers.
- loeg 3y agoIf this was intended to be responsive to my comment, I'm missing the connection.
- pm215 3y agoMy impression is that a lot of the fallout was related to checks in configure scripts and equivalent. That kind of code was traditionally written a lot more sloppily and was much less likely to have the "standard" set of warnings or the make-warnings-into-errors flag enabled. So, yes, quite a lot of packages failed to build, but the offending code was typically not code that was run when somebody was actually using the program and not code where bugs were consequential. And of course Fedora's a big collection of software with a pretty long tail -- 10% of packages likely works out to a lot less than 10% of user-executing-a-program hours.
- spookie 3y agoThe other day I tried to compile a really old program, the Power Crust and its predecessor. It just worked, for some reason I was expecting it not to. I didn't even have the filetype it was supposed to accept, but it was fine, it was just a list of points. One per line. Sanity is such a rare thing these days.
- lelanthran 3y ago> I like how delightfully stable GCC and C is when things from C99 is mandatory only now, a quarter of a century later. This is how stable it is - they were still supporting C constructs from prior to C89 standardisation (which is when, I believe, void pointers were introduced). "maybe consider using void * in more places (particularly for old programs that predate the introduction of void * into the C language). "
- rwmj 3y agoWe've done the work for Fedora now. There was a long thread on the subject at the beginning of the effort which is interesting: https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/CJXKTLXJUPZ4F2C2VQOTNMEA5JAUPMBD/ https://lists.fedoraproject.org/archives/list/devel@lists.fe...
- veltas 3y agoAs someone who doesn't know a lot about distro work, what exactly did you have to do for Fedora? Fix packages that weren't building anymore with this change?
- cozzyd 3y agoAs I understand it, the main problem is autoconf-generated stuff with old versions of autoconf.
- pests 3y agoYep, errors result in autoconf checks failing leading to features being disabled, symbols being removed and more all while the build still succeeds successfully.
- rwmj 3y agoThis was the danger, but I think Florian did something where he compared autoconf output before and after the change and identified problems that way.
- fweimer 3y agoPackages fail to build after a while all the time. The maintainer fixes them (preferably using an upstream patch), and we move on. The issue with this set of compiler changes was that a lot of software builds successfully, but the new build suddenly has expected features missing. This happened with the previous attempt: https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/AL3PXQ3HX26PCF5PYDZ5RU7VJZSBZEPQ/#7I7DBPE6T52TH4XA5SN2DI2HFPAVBYHT https://lists.fedoraproject.org/archives/list/devel@lists.fe... That happened in 2016. In 2019, we undertook another attempt, this time with config.log/config.h diffing, but for various reasons stopped after fixing a few dozen packages, so it never went to the Fedora/upstream proposal stage. After Xcode and Clang the changed the defaults, it was somewhat easier to convince people that we should get Fedora & upstreams ready for a future GCC change. It also helped that Gentoo has been pushing fixes for Clang compatibility upstream for a while (https://bugs.gentoo.org/408963 https://bugs.gentoo.org/408963, with a broader scope in https://bugs.gentoo.org/870412 https://bugs.gentoo.org/870412).
- Joker_vD 3y ago> It is no longer possible to call a function that has not been declared. Heh. Surprisingly, I've seen some people claiming that it's impossible for a language without forward declarations to have a one-pass compiler that could handle calls of not-yet-defined functions, yet pre-ANSI C managed to do exactly that, in a pretty obvious (now that you know that it's in fact possible) way.
- uecker 3y agoWhy surprisingly? In pre-ANSI C is it only possible because there was no type safety. In other languages it could work by deriving the type from the call, but this does not work in C.
- jcranmer 3y agoBut prototype-less functions in C don't actually work all that well. In particular, the inferred return type is going to be an int, so if you're trying to return a 64-bit pointer via a prototype-less function, you're going to have a very bad time.
- Joker_vD 3y agoYes, they don't; it would be possible for a compiler to gather info about such call sites into a list and then, when it's finished compiling a compilation unit, to check this list against the now-complete symbol table and fail if some called functions have mismatched definitions or are still undefined... that's apparently was too much work back when C was designed so we have "hope for the best" design instead.
- mfichman 3y agoI think that would by definition be a two-pass compiler.
- Joker_vD 3y agoNo, you emit all the code in one go, then after you've done that you have some residual pieces of data left one the side: the symbol table, and the list of all the calls of (hopefully)-forward-declared functions. At this point you could run the check on that list against the symbol table, no additional codegen or re-reading the source text needed. Granted, you can call that a second pass although that's not that different from emitting a function's epilogue IMHO.
- Scubabear68 3y agoI really am shocked that GCC took K&R, pre-ANSI C for this long. For Pete’s sake, I was ANSI-fying Aztec C libraries in the late 1980’s!
- layer8 3y agoGCC never cared about Pete.
- kevin_thibedeau 3y agoK&R still exists in lots of the GNU userland apps that see little to no updates.
- lanstin 3y agoMaybe because they got bundled into distros soon and as a critical dependency. For a long time as the internet ramped, portability meant you could pretty quickly edit a few header files or Makefile to make it compile for your particular hardware/OS. But then as the perl build scripts showed and autoconf tried to spread, it became expected to download and compile with no changes. In the context of a distro, maybe that put pressure to provide quick work around to keep old code compiling with minimal changes to the actual source?
- flohofwoe 3y agoTbf, all of those changes should not be surprising to any C programmer for at least the last two decades or so. I'm actually surprised that GCC was so lenient for so long.
- veltas 3y agoIt wouldn't have been surprising to make this the default in the 90's even. The things here are pretty much never done intentionally post-standardisation. And it's easy to force these back to being just warnings to make old builds work if needed.
- iforgotpassword 3y agoThe only annoying thing really is the second strict aliasing example, requiring the temporary variable. It just seems really inelegant. But it has been a pitfall ever since, since using an explicit cast removes the warning (now error) but still breaks strict aliasing. I think the most pragmatic solution is to just compile everything with no strict aliasing. You still get errors for accidental incompatible assignments but won't get bitten by the optimizer.
- omoikane 3y agoImplicit int becoming errors is going to break certain IOCCC entries, including some more recent entries that might have done it due to size constraints, for example: https://www.ioccc.org/years.html#2019_burton https://www.ioccc.org/years.html#2019_burton https://www.ioccc.org/years.html#2019_duble https://www.ioccc.org/years.html#2019_duble
- veltas 3y agoYou can still compile these by downgrading the errors to warnings. They are just errors-by-default now.
- ezekiel68 3y agoNot hostile toward it, but -- if the IOCCC ceased tomorrow, the world would be neither better nor worse off for the loss (in the grand scheme).
- lsllc 3y agoI think it best we keep the folks who like to write that sort of C code busy with the IOCCC rather than out coding in the real world! /s
- veltas 3y agoThis is a long-needed, but gratefully accepted improvement to GCC. I've seen in the wild just how many people see a 'warning' and consider it ignorable, even though many warnings in GCC are certain indication of broken code in pretty much anything new written in the last 35 years. It's important to bear in mind the main change here is to stop wild C programmers from trying to run or release code that's almost certainly broken, because the diagnostic they got wasn't explicit enough. People can still force the old behaviour if they need to.
- deleted 3y ago[deleted]
- marmight 3y ago> The corrected standard C source code might look like this (still disregarding error handling and short writes): > > void > write_string (int fd, const char \*s) > { > write (1, s, strlen (s)); > } And disregarding the passed file descriptor! :)
- fweimer 3y agoHah! Thanks, fixed.
- zzo38computer 3y agoI agree with making most these warnings to be errors by default; actually, they are the same warnings that I had previously thought that should be errors by default (the only exception is incompatible-pointer-types, although that makes sense too to be an error, so it is OK anyways). I think this is good
- TapamN 3y ago>The reason for that is that C does not offer a generic function pointer type, and standard C disallows casts between function pointer types and object pointer types. I've wondered why this hasn't been relaxed a bit when void* is used. Say you have these functions (modified from the link) int compare (const char *a, const char *b) { return strcmp(a, b); } int compare (const void *a1, const void *b1) { const char *a = a1; const char *b = b1; return strcmp (a, b); } And then you have some FP of type int(*compare)(const void *a1, const void *b1) somewhere... If you have correct pointer const-ness, why isn't the first method the PREFERRED way of doing this? It's shorter, more clear, safer (calling compare directly and not through a FP still gets you proper type checking (you wouldn't call either of these example functions directly, but for others you might)), and IDE suggestions can better explain what the function is. I've thought several times about suggesting to compiler writers/the C committee to bless the first method. Is there some obscure hardware that the first would be incorrectly compiled or something (i.e. the calling convention for void* and foo* is different)?
- bubuche87 3y agoI am not sure I understand what you say (despite having read your message several times), but pointers don't have all the same size. Not always, at least. (void* is supposedly big enough to contain any type of pointer, even if I think I remember I read that it's not necessarily true for function pointers). And if the size of "void" and "char" aren't the same you cannot push two void* on the stack and pop two char*. But, like I said: maybe I didn't understood what you said.
- deleted 3y ago[deleted]
- TapamN 3y agoI was wondering if pointer size is still a problem. Hopefully this explanation is clearer: int compare_char (const char *a, const char *b) { return strcmp(a, b); } int compare_void (const void *a1, const void *b1) { const char *a = a1; const char *b = b1; return strcmp (a, b); } int (*call_void)(const void *a1, const void *b1) = &compare_char; //not valid in current C standard Why can't the standard be changed so that it is valid to set call_void to &compare_char, so we don't need to write the longer compare_void? Are there architectures are still in active use where not all pointers are the same size in plain C (not worrying about C++) that would disallow this? AFAIK, x86, ARM, MIPS, SPARC, Alpha, and SuperH would all work fine calling compare_char though call_void. There could be some other issues, like near/far on 16-bit x86, but that would be orthogonal to implicit function pointer void* casting, and could still occur if call_void was set to compare_void. Do one of the more obscure embedded CPUs still in use have a varying pointer size?
- jart 3y agoIt's really sad how the C standards committee is pushing this anti-c89 thing because it breaks one of the most important use cases for C which is writing programs that work under both GCC and JavaScript. See https://justine.lol/sectorlisp2/#evaluation https://justine.lol/sectorlisp2/#evaluation and https://justine.lol/sectorlisp2/#polyglots https://justine.lol/sectorlisp2/#polyglots I've yet to see any explanation of what we stand to gain, by disallowing implicit int. The whole thing just comes across as religious in nature.
- deleted 3y ago[deleted]
- DaveFlater 3y agoIn C++ this is simply legal code. But in C, the bogus warning has now escalated to a bogus error. void fun110 (char const * const *a) {} char **a; fun110(a);