Your C is not standard C

(sebsite.pw)

64 points | by dverlaeckt80 1 day ago

5 comments

  • omoikane 3 hours ago
    GCC still not implementing the standard pragmas reminds me of the old GCC pragma easter egg. This is in cccp.c from gcc 0.9:

        /*
         * the behavior of the #pragma directive is implementation defined.
         * this implementation defines it as follows.
         */
        do_pragma ()
        {
          close (0);
          if (open ("/dev/tty", O_RDONLY) != 0)
            goto nope;
          close (1);
          if (open ("/dev/tty", O_WRONLY) != 1)
            goto nope;
          execl ("/usr/games/hack", "#pragma", 0);
          execl ("/usr/games/rogue", "#pragma", 0);
          execl ("/usr/new/emacs", "-f", "hanoi", "9", "-kill", 0);
          execl ("/usr/local/emacs", "-f", "hanoi", "9", "-kill", 0);
        nope:
          fatal ("You are in a maze of twisty compiler features, all different");
        }
    
    It's gone by gcc 1.21:

        #if 0
        /* This was a fun hack, but #pragma seems to start to be useful.
           By failing to recognize it, we pass it through unchanged to cc1.  */
        ...
        #endif
  • dzaima 4 hours ago
    > gcc, however, always defines FLT_ROUNDS to 1

    Well, considering that gcc doesn't support the FENV_ACCESS pragma, and as such all fesetround calls are always UB, it is valid for it to act as if the rounding mode never changes from the default :)

    • uecker 31 minutes ago
      Well, it is not ok to not support FENV_ACCESS though.
  • MisterTea 1 day ago
    As someone who loves and enjoys writing Plan 9 c, all I can say is "standard c", whatever that means these days, is a mess. Too many things to name ranging from the only two oss compilers being bloated monsters, post c99 additions being pointless, in-line asm ruining portability*, and POSIX can die in a fire (along with typewriter terminals.)

    I have a friend who is currently on the C committee and is likely going to leave because of all the bad ideas which have both been implemented and going to be implemented.

    * No, spamming a million ugly #ifdefs all over the source files is gross and hard to read. Split up the code dammit.

    • pjmlp 3 minutes ago
      The problem starts already with being Plan 9, as it was the evolution of UNIX, the authors even played with Alef as C's successor.

      In what concerns C itself, as the language authors, they did not care that much about the standard, hence why it is mostly C89 with Rob Pike ideas on how header files should be organised, and other Plan 9 specific idioms.

    • WalterBright 1 hour ago
      It would be nice for the C Standard to say that `char` is unsigned. The damage done by making its signedness implementation-defined is incalculable.

      (D sez `char` is unsigned. No more nonsense!)

      C also needs to implement proper arrays. D does it, and that one feature is worth its weight in gold:

      https://www.digitalmars.com/articles/C-biggest-mistake.html

      It's sooo nice to not have array overflows anymore.

    • glouwbug 4 hours ago
      Plan 9 inheritance is really nice. You can still use it with `gcc -fplan9-extensions`. It builds some nice polymorphic workflow an array of tagged enum void pointers
    • 201984 4 hours ago
      >post c99 additions being pointless

      Atomics are necessary nowadays though.

      • 3836293648 3 hours ago
        As a part of the language? Are they really? You can do it all with library functions that just take regular int* (and so on).

        Not that I'd want it, I like complex type systems, but I see it as philosophically consistent with C.

        • aw1621107 3 hours ago
          > You can do it all with library functions that just take regular int* (and so on).

          It's not just about atomics, but you might still find Hans Boehm's 2005 paper "Threads Cannot Be Implemented As a Library" [0] interesting. It describes (among other things) correctness and performance issues that can arise if the compiler and/or language don't know about thread/atomic semantics.

          [0]: https://web.stanford.edu/class/archive/cs/cs240/cs240.1236/o...

          • Joker_vD 1 hour ago
            Yeah, it's all fun and games until you realize that you compiler may invent writes in order to eliminate a branch. Which is rather nonsensical, when you stop to think about it — surely an additional store to global can't be faster than a mispredicted branch?
            • homosapien97 55 minutes ago
              Branch mispredictions on deep machines can be devastating, whereas a store can hit in your L1. Storing can definitely be faster than a mispredicted branch if you think you'll hit in your L1/L2
            • adgjlsfhk1 58 minutes ago
              store to a global is typically 0 cycles (although subsequent reads may be slowed down)
        • jcranmer 3 hours ago
          Compilers have, for several decades now, stopped viewing optimization in terms of optimizing the instructions of a function and instead viewed it as preserving the semantics under some machine model. Consequently, it's rather important to actually nail down some of the object model semantics to be able to do things like understand which loads and stores actually need to be preserved, and you can't really do that with just library functions.

          And, FWIW, in a lot of high-performance code, the overhead of a function call may be too high for an atomic operation.

        • publicmail 3 hours ago
          A library function written in what? Atomics also come along with memory ordering which isn’t otherwise expressible in the language, as in, there was no standard way to do a “load acquire” before, for example.
    • hollowonepl 55 minutes ago
      As much as I’m sentimental about C/C++ myself, changing language spec every 3 years is not what I call standards
      • pjmlp 1 minute ago
        Plenty of industry standards change every few years, that is why a date is part of their identification number.
      • uecker 30 minutes ago
        It does not change every 3 years for C: C99, C11, C17 (minor cleanup), C23.
    • jcranmer 3 hours ago
      There's a lot of post-C99 that has somewhat questionable value (_Generic is very much a we-have-templates-at-home kind of feature), but there are some post-C99 features that I'm rather happy to have:

      * thread_local and _Atomic support from C11

      * fopen("foo", "x") from C11

      * aligned_alloc from C11

      * CMPLX macro from C11

      * C++ attribute syntax ([[foo]] flows better to me than __attribute__((foo)) does)

      * C23 finally gets rid of _Who _The _Hell _Thinks _Users _Like _This-style _Keywords.

      * Multi-level break/continue in C2y

      * Variable declarations in if conditions.

      * Some of the new operations like ckd_add or stdc_rotate_left. I do dislike the names, but meh, at least C is starting to gain a competent set of fundamental operations.

      • Joker_vD 2 hours ago
        Also, C23 (and C2y) is moving ever closer to actually having defined semantics. As in, "just because one branch of your code, for this particular input, has read a value out of an array's bounds, your whole program's execution doesn't suddenly rewinds back to the start (with I/O included) and NOPs/loops infinitely/executes random bytes after main()".

        Also, I'm personally still waiting for a "shift left" operator having cleaner wording in the standard:

            If E1 has an unsigned type, the value of the result is E1 × 2**E2, wrapped
            around. If E1 has a signed type and nonnegative value, and E1 × 2**E2 is
            representable in the result type, then that is the resulting value; otherwise,
            the behavior is undefined.
        
        What does "otherwise" means here? It can't mean "if E1 has an unsigned type, or negative value, or E1 × 2**E2 is unrepresentable, then the behavior is undefined" because the part "if E1 has an unsigned type" has already been covered by the previous clause. Does it mean "If E1 has negative value, or E1 × 2**E2 is unrepresentable, then the behavior is undefined"? Because that's a ridiculous ordinance: (-1) << 1 is UB?!
        • jcranmer 1 hour ago
          > Does it mean "If E1 has negative value, or E1 × 2*E2 is unrepresentable, then the behavior is undefined"? Because that's a ridiculous ordinance.

          That's exactly what it means, see the change mentioned in https://www.open-std.org/jtc1/sc22/wg14/www/docs/n723.htm. (Previously, in C89, left shift on signed integers was completely undefined, only unsigned integers had a defined left-shift operation.) My guess is that it has to do with signed-magnitude representation causing left shifts to be absolute garbage with negative numbers. You also have to remember that the standard having undefined behavior doesn't preclude implementations from defining that behavior, and this is especially true with the freewheeling nature of a lot of the original undefined behavior introduced in C89 and C99.

          > Also, C23 (and C2y) is moving ever closer to actually having defined semantics.

          Eh, having seen how some of the sausage gets made, there's definitely been some cases where the overeager urge to rip out undefined behavior from the standard has worsened the quality.

          • uecker 41 minutes ago
            Well, you could start to help? (instead of just being there to make sure special interests are preserved in voting)

            I also do not see where the quality suffered. There were some rather minor issues we created and need to cleaned up.

            • jcranmer 25 minutes ago
              > (instead of just being there to make sure special interests are preserved in voting)

              This kind of attitude tends to make me suspect that you are disinclined to actually listen to my feedback.

              • uecker 16 minutes ago
                Prove it. Aaron told me explicitly that he was inviting people because he was not happy that there are too many with a user view point. And then a couple people show up that never contribute papers but consistently vote for some special interest.
                • uecker 7 minutes ago
                  Just as a recent example. There is a case where the standard obviously is broken . With a lot of support by most of the committee and actual work by a couple of people there is a paper that aims to fix it. The only people who never constructively helped but still vote against this important repair is your block of other non-contributing people you are part of: https://github.com/c-lang-org/polls/blob/main/n3909.md

                  (Also a counter proposal would be constructive.)

          • Joker_vD 40 minutes ago
            > My guess is that it has to do with signed-magnitude representation causing left shifts to be absolute garbage with negative numbers.

            First of all, why not make it implementation-defined? That seems rather more useful for the programmer. Second, I'm personally aware of only a single machine with signed-magnitude representation, IBM 7090, and its ALS (Accumulator Left Shift) instruction treated the sign bit as sticky; amusingly, LGL (Logical Shift Left) did as well, so you had to put the value being shifted into the MQ register, not the accumulator, when you wanted an unsigned shift. So... no, that can't be it?

            • jcranmer 16 minutes ago
              One of the tricky things is that undefined behavior is commonly understood as having an unlimited "blast radius" and implementation-defined behavior is commonly understood as not permitting that kind of "blast radius." So any extension point that needed that unlimited "blast radius" had to be undefined behavior. (This has changed somewhat in modern times, but it does hold a little bit today--I'd point you to the discussion of N3863 at the most recent meeting, but I don't have any minutes that I feel really captured that discussion well enough.)

              And one of those kinds of "blast radius" is traps, and I suspect the original motivation for making signed integer overflow undefined behavior was precisely to preserve the ability to trap on signed integer overflow, which couldn't be implementation-defined. But the records of the discussions leading up to C89 and C99 are just too fragmentary for me to be certain.

        • SAI_Peregrinus 1 hour ago
          I think it means what you think is ridiculous. Shifting the sign bit out is UB. Unrepresentable results are UB.
          • Joker_vD 1 hour ago
            > Shifting the sign bit out is UB

            Shifting into the sign bit is also UB. Can I please have a language that actually has bitwise operators with defined semantics? No CPUs had differentiated between SAL/SHL for more than 30 years, if that was the original justification.

            I'm fine with left shift trapping on overflow. I'm fine with it just wrapping around. I'm even fine with bitwise shifts simply unapplicable to ints ("cast to unsigned first, then cast back to int"). I am not fine with an operator that invokes UB for literally half of the values of the lhs's domains.

            • uecker 23 minutes ago
              I was thinking about eliminating this shift-into-left bit UB. There was a previous attempt though that failed to get consensus. (but long enough ago that one could try again).

              But all this signed overflow UB can be turned into traps using sanitizers. In practice, generally mandating a trap is difficult though in ISO C. My plan is to mandate this as part of a special mode, in which case I would just leave this UB in the main standard.

              (One problem we also have that some special interests groups in WG14 are rather hostile to our work and the rest of the C community - so I encourage everybody who is actually using C and caring about it to join).

            • dcrazy 1 hour ago
              Swift: https://developer.apple.com/documentation/swift/binaryintege...

              > The << operator performs a smart shift, which defines a result for a shift of any value.

              > - Using a negative value for rhs performs a right shift using abs(rhs).

              > - Using a value for rhs that is greater than or equal to the bit width of lhs is an overshift, resulting in zero.

              > - Using any other value for rhs performs a left shift on lhs by that amount.

              And while the docs don’t say it, `Int.max << 1 == -2`. And in fact, `T.max << 1 == -2` for all `T: BinaryInteger`.

      • monocasa 2 hours ago
        > C23 finally gets rid of _Who _The _Hell _Thinks _Users _Like _This-style _Keywords.

        I always took those to mean "we're sure a bunch of you are already using the keyword we wanted to use as typedef in your codebase, so here's something that's in the standard's namespace, and you can typedef it to something sane for your codebase".

        • uecker 21 minutes ago
          Indeed. And those typedefs were even provided by including stdbool.h

          C23 broke a lot of code by making this proper lower case keywords, we probably should have waited longer.

  • Syzygies 3 hours ago
    ...and I thought Go was standard C!
  • marten31 13 minutes ago
    [dead]