[HN Gopher] Porting to GCC 14: C language issues
       ___________________________________________________________________
        
       Porting to GCC 14: C language issues
        
       Author : fweimer
       Score  : 132 points
       Date   : 2024-02-19 13:43 UTC (9 hours ago)
        
 (HTM) web link (gcc.gnu.org)
 (TXT) w3m dump (gcc.gnu.org)
        
       | actionfromafar wrote:
       | I like how delightfully stable GCC and C is when things from C99
       | is mandatory only now, a quarter of a century later.
        
         | haolez wrote:
         | Didn't some parts of C99 get rolled back on C11 and other newer
         | ones?
        
           | fl0ki wrote:
           | Variable 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 wrote:
             | Uh, 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)
        
               | fractalb wrote:
               | Thanks 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 wrote:
               | The 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 wrote:
               | How do you create a variable array on the heap?
               | 
               |  _confused_
        
               | klodolph wrote:
               | With `malloc`, and converting the pointer to the correct
               | type.                   void function(int n) {
               | int (*arr)[n] = malloc(sizeof(*arr));         }
        
               | actionfromafar wrote:
               | wow, that's wild
        
               | uecker wrote:
               | That is exactly how you create any other type of object
               | on the heap.
        
               | actionfromafar wrote:
               | Snarky or just know a lot? It changes quite a bit how the
               | compiler works. It has to know to make sure malloc gets
               | the array element size argument multiplied in runtime by
               | _n_. To a mere user it broke my mental shorthand of how a
               | C compiler works.
        
               | fl0ki wrote:
               | I'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
               | 
               | If that phrasing isn't accurate I'm sure they'd
               | appreciate an edit.
        
             | fractalb wrote:
             | VLA support is mandatory in C23? I'd like to know the
             | rationale behind this decision. Can you provide any
             | references? Thanks.
        
               | veltas wrote:
               | If 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 wrote:
             | Portable code couldn't use VLAs anyway, it was never
             | supported in MSVC.
        
               | pjmlp wrote:
               | Among others.
        
               | uecker wrote:
               | Portable C code could not use MSVC until recently,
               | because MSVC was stuck with C89 because MS wanted
               | everybody to switch to C++.
        
             | uecker wrote:
             | VLAs 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 wrote:
         | And 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 wrote:
         | I 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 wrote:
           | My 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 wrote:
             | Yet 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 wrote:
               | If this was intended to be responsive to my comment, I'm
               | missing the connection.
        
               | pm215 wrote:
               | My 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.
        
               | jcranmer wrote:
               | I would guess that the main cause of failures is
               | autoconf, which has a habit of relying on _extremely_
               | dubious C code to check for feature availability. On top
               | of that, the compiler runs are done in a way to guarantee
               | that no one will see any warnings, even errors can tend
               | to be silently ignored (!), _and_ a particular version is
               | often baked into a source package, so upgrading autoconf
               | to a more sane version tends to take eons.
        
         | spookie wrote:
         | The 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 wrote:
         | > 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 wrote:
       | We'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.fe...
        
         | veltas wrote:
         | As 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 wrote:
           | As I understand it, the main problem is autoconf-generated
           | stuff with old versions of autoconf.
        
             | pests wrote:
             | Yep, errors result in autoconf checks failing leading to
             | features being disabled, symbols being removed and more all
             | while the build still succeeds successfully.
        
           | fweimer wrote:
           | Packages 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.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, with a
           | broader scope in https://bugs.gentoo.org/870412).
        
       | Joker_vD wrote:
       | > 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.
        
         | jcranmer wrote:
         | But 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 wrote:
           | Yes, 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 wrote:
             | I think that would by definition be a two-pass compiler.
        
               | Joker_vD wrote:
               | No, 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.
        
               | sltkr wrote:
               | There is no need for the second phase. You can record the
               | implicit declaration when you encounter it, and if there
               | is a subsequent declaration, you can check that it's
               | consistent or error out immediately.
               | 
               | This is what C compilers already do, in fact, to produce
               | warnings when an implicit declaration doesn't match a
               | later explicit declaration. But this is a best-effort
               | warning only; it doesn't work if there is no declaration
               | because the function is defined in a different
               | translation unit, as I pointed out above.
        
               | mfichman wrote:
               | Usually, the second pass in a compiler does not re-parse
               | source files. Rather, it operates on another data
               | structure, like an AST, intermediate representation, or
               | the list mentioned in the original comment. At least,
               | that's my understanding of multi-pass compilation.
        
             | kryptiskt wrote:
             | There is no information about the function's prototype in
             | the object file, so the linker can't determine that. The
             | compiler can't either since it's just compiling the
             | compilation unit and has no knowledge of the outside world
             | except what we tell it by prototypes (injected via header
             | files or otherwise), but we aren't doing that here.
        
               | Someone wrote:
               | > There is no information about the function's prototype
               | in the object file, so the linker can't determine that.
               | 
               | That that information isn't there is an implementation
               | choice.
               | 
               | For example, they could have hacked it in like C++ did by
               | mangling names
               | (https://en.wikipedia.org/wiki/Name_mangling). That
               | probably would have required supporting longer
               | identifiers (IIRC archives limited them to 14
               | characters), but that's doable.
        
             | sltkr wrote:
             | This doesn't work in C because the function is not
             | necessarily defined in the same translation unit.
             | 
             | You'd need to do this at link time instead, which would
             | require completely overhauling the format of object files,
             | dynamic libraries, static libraries so they carry
             | information about the types of functions instead of just
             | the symbol names. It's not an easy fix.
        
               | zozbot234 wrote:
               | That's what DWARF is for, and similar formats on other
               | platforms. Also Pascal-lineage languages commonly use
               | this kind of embedded type information to provide module
               | interfaces, it works quite well.
        
               | Joker_vD wrote:
               | > This doesn't work in C because the function is not
               | necessarily defined in the same translation unit.
               | 
               | It _does_ work in C -- that 's what the include files are
               | for (among other things), after all. So it's possible to
               | be able to forward-declare functions outside the
               | translation unit only (extern-declare?), those inside the
               | translation units don't need to even if the compiler
               | works in a single pass. And those external declaration
               | could still be introduced at the very end of the
               | translation unit and still would count. I dunno, seems
               | like a pretty reasonable idea.
        
           | Karellen wrote:
           | Well, there is an argument to be had that, for C, a plain
           | unadorned "int" should probably be the native word size for
           | that architecture. On 64-bit systems, "int" would therefore
           | also be 64 bits.
        
             | _kst_ wrote:
             | I think there are two reasons int has not gone from 32 to
             | 64 bits on 64-bit systems.
             | 
             | Part of it is backward compatibility: code written to
             | assume 32-bit int could break. (Arguably such code is badly
             | written, but breaking it would still be inconvenient.)
             | 
             | Another part is that C has a limited number of predefined
             | integer types; char, short, int, long, long long (plus
             | unsigned variants and signed char). If char is 8 bits and
             | int is 64 bits, then you can't have both a 16-bit and a
             | 32-bit integer type. Extended integer types (introduced in
             | C99) could address this, but I don't know of any compilers
             | that provide them.
        
               | myrmidon wrote:
               | > Extended integer types (introduced in C99) could
               | address this, but I don't know of any compilers that
               | provide them.
               | 
               | What environment are you working in? Because I don't know
               | a single half-recent compiler that does _not_ provide
               | stdint (uint8_t, ..., int64_t), but I mostly work with
               | GCC /LLVM toolchains.
        
               | retrac wrote:
               | Some embedded compilers will provide stdint. And if the
               | compiler doesn't, I've found that one of the first
               | headers written for a project ends up being an equivalent
               | to stdint.
               | 
               | It's pretty common to develop part of an embedded C
               | program under Linux or similar host environment. Better
               | debuggers, better profiling tools, etc. And uint8_t and
               | friends are particularly important when you're working
               | cross-platform.
        
               | dzaima wrote:
               | Extended integer types aren't necessarily related to
               | stdint.h - in the vast majority if not every one of those
               | "half-decent compilers" the stdint.h types are just
               | typedefs over plain old char/short/int/long/long long,
               | which are not extended integer types. Extended integer
               | types is a mechanism to allow having types other than
               | those.
        
               | myrmidon wrote:
               | > in the vast majority if not every one of those "half-
               | decent compilers" the stdint.h types are just typedefs
               | over plain old char/short/int/long/long long, which are
               | not extended integer types.
               | 
               | Sure, but isn't that just an implementation detail?
               | Because I really don't care if my int64_t is internally
               | typedef'd to "long long int" or "__m64", as long as there
               | is a standardized interface to ask for it.
        
               | dzaima wrote:
               | Point being, _kst_'s comment of "I don't know of any
               | compilers that provide them" is correct - there are few
               | if any compilers that actually have actual extended
               | integer types, and thus introducing such in compilers
               | might be non-trivial, and plenty of code may exist under
               | the assumption that they don't exist and thus could break
               | (things like integer promotion rules, _Generic, varargs;
               | and also intmax_t is of mention as it must be at least as
               | wide as any supported standard or extended integer type
               | (which is also why clang/gcc __int128 doesn't qualify as
               | an integer type as per the standard))
        
               | bewaretheirs wrote:
               | If you can have "long long", why not "short short"?
               | 
               | In that alternate universe, char could be 8 bits, short
               | short 16, short 32, and int 64.
        
               | cpeterso wrote:
               | And "long short" and "short long" types. :)
        
               | stkdump wrote:
               | Since the extended integer types are just aliases to the
               | other types, they wouldn't solve the problem. Also in C++
               | these aliases create a problem with overload sets when
               | you mix the two worlds and try to produce portable code.
               | For example long on some platforms is 32 bit and 64 bit
               | on others, also platforms use inconsistent fundamental
               | types for 32 and 64 bit aliases. All in all if you want
               | to produce portable code you neither use those extended
               | integer types nor long. You assume char, short, int, long
               | long are 8, 16, 32, 64 bit respectively, which holds on
               | all relevant platforms.
        
               | dzaima wrote:
               | Extended integer types are decidedly _not_ just aliases
               | to other types - the C standard has separate  "standard
               | integer types" which are the regular
               | char/short/int/long/long long, and "extended integer
               | types" which are any additional implementation-specific
               | types. stdint.h-defined types can be either of those
               | categories (and on regular clang/gcc they're all standard
               | integer types and not extended ones). So you could have a
               | system with char/short/int/long/long long being
               | 8/16/64/64/64-bit respectively and still be able to
               | provide an int32_t that's none of those; just, noone
               | does.
        
               | dataflow wrote:
               | What really sucks about this in C++ is that it prevents
               | you from knowing whether you can overload based on those
               | types.
        
             | hgs3 wrote:
             | I was always a little surprised C never had an integer type
             | the size of a native word. Probably the closest thing would
             | be intptr_t since pointers presumably use a single word to
             | represent addressable memory.
        
               | WalterBright wrote:
               | size_t and ptrdiff_t works for me.
        
               | gumby wrote:
               | It did on the PDP-11
        
               | zozbot234 wrote:
               | > pointers presumably use a single word to represent
               | addressable memory.
               | 
               | Only in flat address spaces, which excludes platforms
               | like old 16-bit x86 or modern CHERI. There, "pointer
               | difference within a single object" need not be the same
               | size as "pointer reference".
        
               | flohofwoe wrote:
               | AFAIK until the switch to 64-bit architectures, int
               | actually was the natural word size. Keeping int at
               | 32-bits was probably done to simplify porting code to
               | 64-bits (since all structs with ints in them would change
               | their memory layout - but that's what the fixed-width
               | integer types are for anyway, e.g. int32_t).
               | 
               | In hindsight it would probably have been better to bite
               | the bullet and make int 64 bits wide.
        
               | garaetjjte wrote:
               | int being originally native word size is the reason for
               | weird integer promotion rules.
        
               | dataflow wrote:
               | > AFAIK until the switch to 64-bit architectures, int
               | actually was the natural word size
               | 
               | 32-bit int is still arguably the native word size on x64.
               | 32-bit is the fastest integer type there. 64-bit at the
               | _very least_ often consumes an extra prefix byte in the
               | instruction. And that prefix is literally called an
               | "extension" prefix... very much the opposite of native!
        
               | bonzini wrote:
               | 32-bit also often has the prefix byte (if one of the
               | operands is r8-r15 or, for extending moves from 8-bit
               | registers, r4-r15)
        
               | edflsafoiewq wrote:
               | int is the smallest type you can do ALU ops on, so as
               | long as x64 can still do 32-bit arithmetic, it's
               | "natural" for it to remain 32 bits.
        
             | WalterBright wrote:
             | Yeah, except that ints in your data structures will
             | unnecessarily consume far, far too much memory.
        
               | flohofwoe wrote:
               | OTH, the explicitly sized integers (int32_t, int16_t etc)
               | had been added already in C99 (e.g. 25 years ago), and
               | should be used when a specific memory layout of structs
               | is desired.
        
               | WalterBright wrote:
               | If you're going to use those, then there's no reason to
               | have ints be 64 bit.
               | 
               | Personally, I find using int32_t in general to be an
               | uglification of the code. I never use `long` in C code
               | anymore, as it's sometimes 32 bits and sometimes 64 bits.
               | I use `int` and `long long`.
               | 
               | Do I care about 16 bit code anymore? No. Very few
               | programs would port between 16/32 these days anyway, no
               | matter what the Standard says or how hard you try to
               | write code portably.
        
               | extraduder_ire wrote:
               | Always thought it was odd how "long long" is one of the
               | only common types that's two words with an implicit int
               | at the end.
               | 
               | Almost makes me want to add "typedef long long longer;"
               | to some code that I don't intend anyone to maintain.
        
               | cpeterso wrote:
               | Google's C++ style guide similarly recommends using 'int'
               | in general and never using 'int32_t', though it
               | recommends using 'int64_t' for bigger numbers instead of
               | 'long':
               | 
               | https://google.github.io/styleguide/cppguide.html#Integer
               | _Ty...
        
               | LeFantome wrote:
               | Is it the numbers you do not like? If you use "long
               | long", it must not be the length.
               | 
               | I don't love long long. As an amateur compiler writer, it
               | hurts me. "long long" makes "long" both a modifier ( like
               | unsigned is ) and a type. Yuck.
               | 
               | I wish it was i8, i16, i32, and i64 ( with u versions of
               | each ). f32 and f64 for floats. Those are easy to
               | understand and fairly easy on the eyes.
               | 
               | If those numbers are too noisy, the CIL ( .NET ) types
               | could work. For example, i4 and i8 instead of i32 and
               | i64. I do no love the look of i1 either though. I guess
               | you could special case sbyte and byte as aliases.
        
               | dzaima wrote:
               | "long" is always a modifier, just potentially applied
               | twice, and potentially to nothing. A more written out
               | version of "long long" is "long long int", and similarly
               | "long" is really "long int".
        
               | Gibbon1 wrote:
               | I essentially do not use int short, long, long long at
               | all. Frankly I think those were a terrible mistake and
               | people should avoid using them.
        
               | Karellen wrote:
               | If 32-bit ints didn't make your structures consume far,
               | far too much memory in the early '90s, when consumer-
               | grade computers came with 4-8 _M_ iB of RAM and 256 _M_
               | iB disks, then I don't see how 64-bit ints could have
               | done so in the mid-'00s when they came with 1GiB of RAM
               | and 256GiB disks.
        
               | gpderetta wrote:
               | They still come with 64k (or so) of L1 cache.
        
             | bee_rider wrote:
             | If the programmer didn't specify the size of an int, it
             | should mean "dealer's choice." Let the compiler pick a
             | default, better yet make it a compiler option.
             | 
             | Fortran is, as always, ahead of the game.
        
             | fweimer wrote:
             | I would argue that the native word size is still 32 bits on
             | x86-64, though. With many instructions, using 64-bit
             | registers needs a segment prefix override. Some RISC
             | architectures do not even have 32-bit zero-extending
             | integer instructions, so for them, 64-bit as the native
             | word size makes sense. On the other hand, I'm not sure if
             | <stdint.h> and uint16_t were invented at the time, and
             | "short short int" is not valid syntax (even today), so
             | there wasn't an obvious way to denote a 16-bit integer
             | type.
        
           | bewaretheirs wrote:
           | Historically they worked well enough (on 16-bit and 32-bit
           | machines) because sizeof(pointer) == sizeof(int) ==
           | sizeof(general register) on the architectures where C
           | flourished in the pre-ANSI C era.
           | 
           | But with the migration to 64-bit machines, typically int
           | stayed put at 32 bits.
           | 
           | I guess nobody wanted to introduce a new integral type
           | between short and int; they had enough trouble dealing with
           | code which assumed sizeof(long) == 4. I recall stumbling
           | across a comment where the word "beint32_t" appeared where
           | "belong" would have made sense in context..
        
             | torstenvl wrote:
             | I love naive search and replace errors. In the November
             | 1996 version of the Defense Incident-Based Report System
             | codes definitions in DoD Manual 7730.47, the code 092-C2
             | refers to "shallful" dereliction of duty.
        
             | Joker_vD wrote:
             | Why not just make short 32-bits? Yeah, you lose the type
             | for the 16-bit wide integers but x64 doesn't natively
             | support it all too well anyway, unlike the 32- and 64-bit
             | wide integers. And that is what the C integer types are
             | about, right, about being efficiently represented by the
             | underlying hardware, not their exact bitwidth? Right?
        
               | GrumpySloth wrote:
               | No. It's for tightly packing data in data structures.
               | Bitwidth is exactly what's important here.
        
               | Joker_vD wrote:
               | Well, that's a shame because bitwidth of standard integer
               | types is quite uncertain. CHAR_BIT can be (and is, on
               | some platforms) 16 or 32, long was never guaranteed to be
               | 64 bits (it's quite often was 32 bits on platforms with
               | 16 bit ints) etc, not to mention that if _that_ is what
               | the standard integer types were for then they 'd probably
               | have names like int8/uint8/int16/int32/etc.
               | 
               | It's almost as if they were not, in fact, intended for
               | precise control of bitwidths in portable manner...
        
               | GrumpySloth wrote:
               | It doesn't matter what someone in the past thought they
               | were for. That's what they are for in practice, names are
               | irrelevant here, yes, they are quite bad. But the ones in
               | stdint.h are just typedefs for those, so that's what we
               | are left with.
        
               | oconnor663 wrote:
               | > And that is what the C integer types are about, right,
               | about being efficiently represented by the underlying
               | hardware, not their exact bitwidth?
               | 
               | If you have one `short` argument to your function maybe.
               | But if you have a `short[]` array, you probably do care
               | about the memory layout of that array. You might need it
               | to be compatible with some particular data format that
               | you're trying to read/write. Same with a field of a
               | struct, if that struct is used for parsing. A lot of C
               | code does parsing like this.
        
               | kevin_thibedeau wrote:
               | The efficient hardware types are handled by int_fast*_t.
               | The legacy types can't be redefined outside their
               | established ranges because that would break things that
               | depend on them fitting into a known amount of memory.
        
             | garaetjjte wrote:
             | It would still break on struct and floating point type
             | returns though.
        
             | sgerenser wrote:
             | Also known as a clbuttic mistake.
        
         | NelsonMinar wrote:
         | Oh this makes me feel old. When I first learned C declaring
         | functions was very much an optional feature. A good idea, but
         | optional.
        
         | WalterBright wrote:
         | In the C compiler I wrote a couple years ago (ImportC), since
         | it was re-using the D semantic routines, forward referencing
         | functions "just works".
         | 
         | In fact, it works so well you don't even notice it working. If
         | the C committee wants to improve C, they should make this an
         | official feature.
         | 
         | P.S. Because of the lack of forward referencing, C code tends
         | to be organized as leaves first, and the entry point at the
         | end. This is simply backwards, the entry point should be at the
         | beginning.
        
           | cpeterso wrote:
           | Are there any compilers our language that parse source files
           | in reverse order? Then you could have a one-pass compiler
           | without forward declarations (though you would need
           | "backward" declarations). :)
           | 
           | Someone shared with me their idea for a parallelized parser,
           | where threads would parse different segments of the source
           | file and then stitch their incomplete ASTs together.
        
         | uecker wrote:
         | Why 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.
        
         | dataflow wrote:
         | Well it depends on the calling convention. They're right in the
         | sense that this can't be done for arbitrary calling
         | conventions.
        
       | Scubabear68 wrote:
       | I 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 wrote:
         | GCC never cared about Pete.
        
         | kevin_thibedeau wrote:
         | K&R still exists in lots of the GNU userland apps that see
         | little to no updates.
        
         | lanstin wrote:
         | Maybe 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 wrote:
       | Tbf, 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 wrote:
         | It 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 wrote:
         | The 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 wrote:
       | Implicit 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_duble
        
         | veltas wrote:
         | You can still compile these by downgrading the errors to
         | warnings. They are just errors-by-default now.
        
         | ezekiel68 wrote:
         | Not 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 wrote:
           | I 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 wrote:
       | This 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.
        
       | marmight wrote:
       | > 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 wrote:
         | Hah! Thanks, fixed.
        
       | zzo38computer wrote:
       | I 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 wrote:
       | >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 wrote:
         | I 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.
        
         | dzaima wrote:
         | C11:                   A pointer to void shall have the same
         | representation and alignment requirements as a pointer to a
         | character type. [48]         <blah blah pointers to qualified
         | vs unqualified, structs, and unions also have the same
         | representation between themselves>.         Pointers to other
         | types need not have the same representation or alignment
         | requirements.                  [48]: The same representation
         | and alignment requirements are meant to imply
         | interchangeability as arguments to functions, return values
         | from functions, and members of unions.
         | 
         | The thing I've heard for systems where e.g. an int* and char*
         | aren't equal is where the "int*" is the "primary" pointer type,
         | and a char* has to add back the would-be-leading-bits or
         | something. Though as per the above quote, char* vs void* would
         | be safe? Doesn't help anything other than char* though.
        
           | erik_seaberg wrote:
           | There used to be hardware that supported pointers to
           | individual bits or complete words, rather than the eight-bit
           | bytes we see today.
           | 
           | https://en.wikipedia.org/wiki/Word_addressing
        
       | jart wrote:
       | It'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
       | and 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.
        
       | DaveFlater wrote:
       | In 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);
        
       ___________________________________________________________________
       (page generated 2024-02-19 23:02 UTC)