[HN Gopher] C23 is finished: Here is what is on the menu
       ___________________________________________________________________
        
       C23 is finished: Here is what is on the menu
        
       Author : ingve
       Score  : 233 points
       Date   : 2022-08-05 06:42 UTC (16 hours ago)
        
 (HTM) web link (thephd.dev)
 (TXT) w3m dump (thephd.dev)
        
       | forrestthewoods wrote:
       | > We do not have namespaces in C, which means any time we add
       | functionality we basically have to square off with users.
       | 
       | Has anyone seriously proposed adding namespace to C? This feels
       | like an obviously good addition to me. What's the argument
       | against it?
        
         | Jensson wrote:
         | In C you can use functions the file doesn't see, and since
         | namespaces changes what symbol an expression refers to
         | depending on what symbols exists in the program it will lead to
         | a lot of strange scenarios. C++ doesn't allow this, and is the
         | main way C++ isn't compatible with C.
        
         | raverbashing wrote:
         | I'm skeptical of it. I think it is a
         | 
         | std::opinion::bad_idea()
         | 
         | ;)
        
         | jstimpfle wrote:
         | Namespaces are often called for especially by programmers used
         | to C++ but doing C. They seem like such a simple feature to
         | implement. I'm still not sure I like them:
         | 
         | - What is the benefit of using A::B::C versus A_B_C, really, in
         | terms of avoiding name clashes? I can point out one immediate
         | benefit of doing the latter - code is easier to read because
         | line noise is reduced (perhaps subjective but I think it will
         | be hard arguing the other way around).
         | 
         | - Is it a good thing that half the code will now refer to
         | A::B::C using only B::C or even only C? Without assistance of a
         | program with good semantic insight (a solid IDE, etc) this only
         | makes identifier search harder.
         | 
         | - Namespaces "enforce" discipline in one way - you can be sure
         | that the symbols at the binary level will be properly prefixed.
         | But with only a little discipline programmers can do the
         | prefixing themselves, and in return they can move code between
         | files (/namespaces) more freely, which is good for refactoring.
         | 
         | And as a sibling commenter pointed out, one factor why
         | namespaces get called for could be lack of understanding that
         | we can hide the "guts" of any function (or variable) using
         | "static" linkage. There is less discipline (prefixes..) needed
         | for these functions because they are only visible in the
         | current translation unit.
        
           | pjmlp wrote:
           | Namespaces can be assigned alias to avoid colisions, good
           | luck doing that to A_B_C.
        
             | jstimpfle wrote:
             | Good point, but then again I'm not that sure it's a net
             | benefit because I like everything in a codebase to refer to
             | a given object using the exact same name.
             | 
             | I suppose aliasing is useful for widely used libraries, for
             | example if a big software projects wants to include two
             | different versions of the same library. Or Team A wants to
             | rename their module but make it easier for other teams that
             | use their module to follow suit.
             | 
             | All that can provide some ease of use in the short term but
             | produces more mess to clean up in the long term. IMHO.
             | 
             | Real name clashes between two different libraries are quite
             | unlikely, and namespaces would only solve that problem on
             | the source code level, not on the binary / symbol level.
        
           | j-krieger wrote:
           | Namespaces can enforce good project structure. If you're
           | using A::B::C and A::B::D you can be 100% that both C and D
           | definitely live under B, and your editor can work with that
           | as well.
        
             | jstimpfle wrote:
             | > you can be 100% that both C and D definitely live under B
             | 
             | And that brings you what benefit exactly? I've seen a
             | number of projects that are preoccupied about "proper
             | nesting", while it is 99% bureaucratics and all those
             | projects are still a mess.
             | 
             | The benefits of "living under this or that" are technically
             | zero, and with respect to human factors are minimal given
             | that 1) you can get most of the organizational benefits of
             | namespaces by doing A_B_C, and 2) you can also add new
             | members to proper namespaces from external files in most
             | languages, including C++, so there really isn't a
             | difference w.r.t "knowing for sure".
        
             | astrange wrote:
             | Well, they enforce having a lot of project structure. Java
             | and C# use their namespaces to ensure that everything
             | useful is under five layers of pointless names like
             | System.Collections.com.net.org.ArrayList.
             | 
             | I think not having namespaces may be an effective way to
             | prevent enterprise programming from happening.
        
           | lifthrasiir wrote:
           | > What is the benefit of using A::B::C versus A_B_C, really,
           | in terms of avoiding name clashes?
           | 
           | Many C programs do not use hierarchical names like `A_B_C` as
           | they ideally should. They instead pick a short identifier
           | that is (often incorrectly) believed to be distinct enough.
           | 
           | > Is it a good thing that half the code will now refer to
           | A::B::C using only B::C or even only C? Without assistance of
           | a program with good semantic insight (a solid IDE, etc) this
           | only makes identifier search harder.
           | 
           | If you meant that the identifier search should be possible
           | with just grep, yeah namespace makes it harder but it's not
           | the only cause and this doesn't explain why many other
           | languages with less overall IDE support than C/C++ have
           | namespaces. And I believe it is possible to design a
           | namespace system that only needs a ctags-level automation for
           | proper identifier search, though I don't know how you feel
           | about ctags.
        
             | [deleted]
        
             | coliveira wrote:
             | > many other languages with less overall IDE support than
             | C/C++ have namespaces.
             | 
             | Because they are not C-style language. In Python or Java,
             | for example, you need a separate file to create a package.
             | In C++ you can add namespaces anywhere you want. This makes
             | C++ namespaces harder to maintain even with automated
             | tools.
        
             | jstimpfle wrote:
             | > Many C programs do not use hierarchical names like
             | `A_B_C` as they ideally should. They instead pick a short
             | identifier that is (often incorrectly) believed to be
             | distinct enough.
             | 
             | What is your list of name clashes that you experienced? How
             | many real headaches did they give you? Or is it all a non-
             | problem? Not a rhetorical question - I'm mostly working on
             | smaller projects < 100KLOC.
             | 
             | > I don't know how you feel about ctags.
             | 
             | I use ctags from time to time when I have to, but I still
             | don't like it when there are multiple namespaces that
             | contain the same set of names which confuses navigation
             | operations, even vim with ctags I think. Maybe even IDEs
             | like Visual Studio, I'd have to check though what works and
             | what doesn't.
        
               | forrestthewoods wrote:
               | > What is your list of name clashes that you experienced?
               | 
               | My experience is that sharing libraries is so wildly
               | difficult In both C and C++ that code is not shared and
               | wheels are reinvented. This has more to do with build
               | systems than namespaces. But namespaces are a factor.
        
               | Morpholemew wrote:
               | > What is your list of name clashes that you experienced?
               | How many real headaches did they give you?
               | 
               | Here are a dozen examples from Xlib, where Windows
               | happened to choose the same short common names for many
               | things: https://gitlab.freedesktop.org/xorg/proto/xorgpro
               | to/-/blob/m...
               | 
               | ... and IIRC this list is no longer sufficient; you need
               | to add to it in order to compile with the current Windows
               | SDK. (Upon closer inspection, it was updated two weeks
               | ago, so maybe it's fine... until the next Windows SDK
               | update.)
               | 
               | I'll admit that this happens less often on Linux, where
               | the system headers are smaller (and everybody uses X11 so
               | there's historic reason to avoid all the short names that
               | were gobbled up by Xlib in the '80s), but I've still run
               | into occasional clashes between different library
               | headers, or between legacy code and updated headers. eg.
               | bool/Bool/BOOL are common collisions among pre-C99
               | libraries (including libraries that require C99 but don't
               | remove the old names for backwards compatibility
               | reasons), as well as min/MIN/max/MAX which still aren't
               | in standard C as far as I can tell.
               | 
               | The headaches it gives are real, but not large in the
               | grand scheme of things. The lack of defer (or otherwise
               | standardized and cross-platform __attribute__(cleanup) )
               | is a bigger headache, for example.
        
               | lifthrasiir wrote:
               | > What is your list of name clashes that you experienced?
               | How much real headaches did they give you? Or is it all a
               | non-problem? Not a rhetorical question - I'm mostly
               | working on smaller projects < 100KLOC.
               | 
               | I too refrained from using C for that large software so I
               | don't have many examples either, but in one case I was
               | using TweetNaCl where you have to supply `extern void
               | randombytes(unsigned char*, unsigned long long)` for
               | CSPRNG and I had to rename it for some reason I can no
               | longer recall.
        
               | rwmj wrote:
               | I've programmed C for 35 years, and namespaces clashes
               | have happened for me exactly once. There are two JSON
               | libraries that use json_* as a prefix and have
               | conflicting symbols. This actually caused a quite
               | difficult to track down bug:
               | https://bugzilla.redhat.com/show_bug.cgi?id=2001062
               | 
               | However this is not a reason to add namespaces. (In fact
               | the bug was fixed using symbol versioning, an already
               | existing feature of ELF.)
        
         | mgaunard wrote:
         | maybe we could call the language with such extensions "C++"
        
           | mort96 wrote:
           | Except that C++ doesn't serve that role, C++ is its own whole
           | thing. I think "C but with namespaces and a method call
           | syntax (and templates?)" would be a great language which
           | would occupy a completely different space than C++.
        
             | jcelerier wrote:
             | if you introduce namespaces and method calls you have to
             | introduce name mangling, to differentiate
             | namespace foo { void x(); }
             | 
             | and                   namespace bar { void x(); }
             | 
             | and then you have to rely on compiler vendors to use the
             | same mangling everywhere otherwise you end up exactly at
             | the C++ position where there are multiple incompatible name
             | mangling schemes, thus C code compiled with e.g. cl.exe
             | would not be able to call a C function compiled with gcc
             | (and FFIs wouldn't be able to either so you loose the "easy
             | language bindings" "feature" of the operating system ABIs)
        
               | mort96 wrote:
               | You do realistically need a kind of name mangling yes, if
               | you want to keep using [a-zA-Z0-9_] as the set of
               | characters in symbol names. But without overloading or
               | other fancy stuff, you could have a super simple name
               | mangling scheme: separate the names by a double
               | underscore "__".
        
               | jstimpfle wrote:
               | Namespaces by themselves aren't a reason why name
               | mangling is needed. There isn't a technical reason why
               | foo::x couldn't be the symbol name, literally. (Not sure
               | what ELF and PE/COFF etc would think of these names in
               | current implementations).
               | 
               | Name mangling is needed if you want to overload functions
               | and qualify them only by the types (not names) of their
               | in and out parameters. This is where it gets ugly on the
               | binary level.
        
               | jcelerier wrote:
               | > There isn't a technical reason why foo::x couldn't be
               | the symbol name, literally.
               | 
               | how do you do when you want to access your C function
               | from a language which is binary-compatible with C but
               | uses :: for something else? [a-zA-Z_][a-zA-Z0-9_]*
               | identifiers are the only thing that the whole world more-
               | or-less standardized around.
               | 
               | e.g. in fortran you can directly import a C function and
               | call it. But "::" in the middle of a function name would
               | very likely fail (I don't know enough fortran to tell for
               | sure but given how the syntax looks...):
               | subroutine foo (a, n) bind(c)            import
               | real(kind=c_double), dimension(*) :: a            integer
               | (kind=c_int), value :: n          end subroutine foo
               | 
               | allows to call a "void foo(double *a, int n);" function
               | defined in C. I imagine that
               | subroutine ns::foo (a, n) bind(c)
               | 
               | would likely not work
        
               | zozbot234 wrote:
               | > how do you do when you want to access your C function
               | from a language which is binary-compatible with C but
               | uses :: for something else?
               | 
               | Typically you'd do this by picking a different internal
               | name for the function, and putting the external symbol in
               | quotes.
        
               | jstimpfle wrote:
               | Fair point. The C naming convention with flat names is
               | quite conservative and consequently allows the names to
               | be used directly from most other languages without any
               | language-aware mapping / compatibility layer.
        
               | zozbot234 wrote:
               | Rust doesn't allow function overloading. Name mangling is
               | used there to implement linking multiple versions of an
               | external module into the final build.
        
               | tialaramex wrote:
               | Although Rust doesn't have ad-hoc polymorphism, it does
               | have polymorphism, and so it needs to track multiple
               | versions of the same function anyway.
               | 
               | Take String::contains(). The equivalent feature in C++ is
               | an overloaded function, so there are I believe it's
               | three, versions of this function which take different
               | parameters: A string, a char and a pointer to chars, they
               | do similar things in practice but the compiler has no
               | idea, there are just three functions with the same name.
               | However the Rust feature is polymorphic, there are N
               | versions of this function depending on what
               | monomorphisations are chosen at compile time. The
               | compiler knows these are all the same function, but the
               | parameters have different types _in practice at runtime_
               | and so the generated machine code is different. If your
               | program can String::contains(cat_photo_jpg) then the
               | compiler will produce the code to call it with your Jpeg
               | type or whatever and it will need to keep it distinct
               | from the version where it takes a String or a char or
               | whatever.
               | 
               | Rust does this more often than C++ because it cannot
               | choose ad hoc polymorphism. So if there should be a foo
               | function which can take parameters bar, baz or quux, we
               | need to decide either that bar, baz and quux all
               | implement some trait which foo takes, _or_ we need three
               | separate functions foo_with_bar, foo_with_baz and
               | foo_with_quux.
        
               | throwaway17_17 wrote:
               | This question is very OT considering the what TFA is
               | about, but...
               | 
               | What does the term ad-hoc polymorphism mean in your
               | comment? Are you saying Rust does not have ad-hoc
               | polymorphism because the syntax acts like the function in
               | parametrically polymorphic and only once monomorphisation
               | occurs are the different functions per argument type are
               | generated is it N different functions. Does this line of
               | thinking also say Haskell does not have ad-hoc
               | polymorphism?
               | 
               | And in case any reader wonders, I am genuinely interested
               | in your response to these questions (i.e. this isn't bait
               | nor rhetorical).
        
               | JoelMcCracken wrote:
               | Indeed; I am wondering why go does not consider traits in
               | rust a form of ad hoc polymorphism?
        
               | [deleted]
        
               | tialaramex wrote:
               | Ad hoc polymorphism is distinct from parametric
               | polymorphism in that rather than parameter type itself
               | being a parameter, there are just an arbitrary (ad hoc
               | determined) set of types allowed.
               | 
               | The C++ standard library defines contains(x) so that
               | it'll work for a string x, a single char x or a pointer
               | to chars x. Nothing else can work, those are the
               | arbitrary list of types which work, that's ad hoc
               | polymorphism.
               | 
               | A separate implementation is provided for each of those
               | three cases, which is why if I've got a JPEG, I can't
               | instead ask if the string contains the JPEG, that's not
               | one of the three implementations provided.
               | 
               | The Rust standard library just defines the type of x in
               | terms of a trait, Pattern. So, my Jpeg type can just
               | implement Pattern and now I can ask if Strings contain
               | the Jpeg and that works because contains delegates the
               | matching problem to Pattern and Jpeg implements Pattern
               | -- _only_ the specific idea of  "contains" as distinct
               | from "begins with" or "split" or a dozen other functions
               | is handled by the contains function.
               | 
               | I am not a (serious) Haskell programmer but I would argue
               | that Haskell also lacks ad hoc polymorphism here as I
               | understand it (and to be clear: I think this is in
               | general a good or at worst reasonable choice)
               | 
               | The place where ad hoc really shines is when you're got a
               | function that say, makes complete sense with exactly two
               | (or maybe three, fewer than two is irrelevant and more
               | than three seems unlikely to provide reasonable
               | ergonomics) specific types, which otherwise have nothing
               | useful in common.
               | 
               | For example suppose I've got a whole variety of bird
               | types, Goose, Chicken, Ostrich, Penguin, Sparrow and so
               | on, and I've got this function thunk() and I realise
               | that, oddly it makes sense to thunk a Sparrow or an
               | Ostrich, but literally no other birds at all. I think
               | hard about it, but the best I can come up with to
               | describe this property is "Thunkable" since all it really
               | means is you can thunk() them. And there's no "content",
               | there's no special implementation work in "Thunkable"
               | that shouldn't live in thunk() for maintenance anyway. In
               | this case ad hoc polymorphism is great because it saves
               | needing to make this stupid "Thunkable" trait / type
               | class / type-of-types / interface just to group together
               | Sparrow and Ostrich for this single purpose.
               | 
               | But I'd argue the "Thunkable" case is rare, and C++ has a
               | lot of cases where ad hoc polymorphism was the wrong
               | choice and they fell into it.
               | 
               | I mentioned three types, really C++ contains() does only
               | two things but it spells one of them two ways for 20+
               | year old reasons. It can do strings (as std::string, but
               | also via C's char * type) and it can do a single
               | character char (one code unit, so, no poop emoji).
               | 
               | Rust provides four Patterns, they are a string reference,
               | a single char (a Unicode scalar, so yes a poop emoji
               | works), a slice of chars (any of the chars matches), or a
               | predicate which matches characters.
               | 
               | Now, do any of those four feel like things you'd
               | definitely never want in C++? Because if C++ wanted all
               | of them that's now five overloads for contains. And five
               | overloads for find, and for every other matching
               | function, on every string or string-like type...
               | 
               | I believe ad hoc polymorphism is so rarely what you
               | _really_ want, and yet it so often detracts from the rest
               | of the language facilities that  "We don't have that" is
               | a sensible language design choice, same as for multiple
               | inheritance.
        
               | throwaway17_17 wrote:
               | I think my issue is with your definition. Type classes in
               | Haskell are the implementation for ad-hoc polymorphism,
               | and likewise traits in Rust. I think the definition you
               | are using is not the commonly given one and that is where
               | my confusion came from.
               | 
               | Typeclasses (in a Haskell context) we're formalized in
               | the Wadler and Blott paper "How to make ad-hoc
               | polymorphism less ad hoc". And for Rust, in [1] Traits
               | are explicitly stated to be the method in which Rust
               | achieves ad-hoc polymorphism.
               | 
               | 1: Klabnik, Steve; Nichols, Carol (2019-08-12). "Chapter
               | 10: Generic Types, Traits, and Lifetimes". The Rust
               | Programming Language (Covers Rust 2018)
        
               | tialaramex wrote:
               | I didn't know either of these things, I guess now I have
               | some reading to do, so thanks.
               | 
               | Edited to add: Hmm. Actually though, surely that second
               | reference is just "The Book" as it's called, did it
               | _actually_ say it 's about ad hoc polymorphism? Because
               | I've read this section of The Book, although I hadn't in
               | 2018, and it doesn't mention "Ad hoc polymorphism".
               | 
               | What's there now (I just checked) is a description of how
               | you'd approach this problem in Rust, using traits, but
               | doesn't claim this is ad hoc polymorphism, and sure
               | enough doesn't involve an arbitrary set of types which is
               | the sort of the point of why "ad hoc" is there in the
               | name.
        
         | quelsolaar wrote:
         | Yes people have floated the idea. I argued against it, I was
         | asked at the wg14 meeting to explain my position and the
         | following was my reply:
         | 
         | I was asked during the meeting why I think that name spaces are
         | a bad idea, so here is my thoughts. I must warn you that the
         | following may sound like an anti C++ rant, because well .... it
         | is.
         | 
         | Name spaces have several bad aspects. The majority of them are
         | that they make the code a lot less readable.
         | 
         | First, If I see a function call in source code, I cant assume
         | what function is called. There may be a namespace somewhere
         | above or in a header-file somewhere that changes the meaning of
         | the code. It matters a lot for instance when you copy code from
         | one file to another, and all of a sudden the code just means
         | something completely different. Name spaces add state that
         | changes the meaning of the code around it. When your code isn't
         | doing what it should and you are trying to figure out why, you
         | need to be able to trust that the code you are reading on
         | screen is actually what you think it is. I dislike it for the
         | same reason i dislike overloading: it makes it less clear what
         | things you are reading really are.
         | 
         | (It may be argued that you already cant trust your eyes in C,
         | because you can do lots of devious things with the per-
         | processor, and to that I say, yes its bad enough, so lets not
         | add more of it)
         | 
         | Name spaces splits the identifier in half, but its still a
         | unique identifier.
         | 
         | my_module::create();
         | 
         | is and has to be as unique as:
         | 
         | my_module_create();
         | 
         | So what are we accomplishing? Do we have the same level of
         | collisions? No, in fact we have more collisions because the
         | parts can collide individually! if there are multiple my_module
         | they will collide, and if there are multiple create in
         | different namespaces they will collide!
         | 
         | my_module::create(); // one name space my_module::destroy(); //
         | another name space
         | 
         | Collision! how about:
         | 
         | using namespace my_module; // namespace with a create using
         | namespace my_other_module; // namespace with a create
         | 
         | crate();
         | 
         | Collision! If we instead write plain old C:
         | 
         | my_module_create(); my_module_destroy();
         | 
         | No collision.
         | 
         | my_module_create(); my_other_module_create();
         | 
         | No collision.
         | 
         | Beyond creating new opportunities for collisions, we create
         | confusion by making it possible to hide half the identifier
         | somewhere else. Yes, it can save some typing, but programing is
         | never hard because of typing, programing is hard because its
         | hard to understand what is going on. Namespaces solves the easy
         | stuff, by making the hard stuff harder.
         | 
         | Its the age old, trap of C++ of adding something clever out of
         | convenience, that turns out to be unclear and something that
         | the programmer has to manage.
         | 
         | Further, namespaces encourage people to use short common names
         | even further, because they think it saves them typing, and they
         | think if it goes wrong they can always manage it with
         | namespaces, and then they end-up having to manage something
         | they shouldn't have to manage in the first place.
         | 
         | I have never had a namespace collision in 20+ years of C
         | programming. Why? Because I use long descriptive names, that
         | always start with where the functionality resides. Its
         | readable, straight forward and works. Namespaces is a
         | complicated system for managing a problem that should never
         | happen in the first place, unless the user is very careless. We
         | should not encourage carelessness.
         | 
         | I think C, should take some blame for this being a problem
         | because the standard library have far shorter names then is
         | advisable. It has made people think that names like: "jn" or
         | "erf" are good examples of unique, clear and descriptive
         | naming. The added wrinkle of "significant characters" has made
         | people think that C mandates short names, something that no
         | major implementation requires. There also seems to be a
         | persistent belief that display technology has not yet evolved
         | to the level that we can display more then 80 characters per
         | line and that we therefor need to use short cryptic names for
         | everything. This is also an argument from a different age.
         | 
         | Namespace collision in C almost never happens between 3rd party
         | libraries, it is almost the exclusive problem of the standard
         | library because it is so poorly named. If we want to fix this,
         | by hiding the standard library behind a namespace, we might as
         | well just add a c_standard_lib_ prefix to all functions, and
         | keep the garbage that is namespaces out of C. Not that we would
         | do either, since both world break backwards compatibility. So
         | why would we add namespaces if it wouldn't be used by the
         | standard lib, the very library we have name collision problems
         | with? In fact if we added an optional namespace for the
         | standard lib, all we would accomplish is to pollute the
         | namespace with one more identifier.
        
           | einpoklum wrote:
           | This is a misunderstanding of C++ principles, not an anti-C++
           | rant.
           | 
           | In C++, it is intentionally not straightforward to know what
           | code gets called when a statement is executed - even without
           | namespaces. We have:
           | 
           | * Function overloads
           | 
           | * Non-trivial (and not-built-in) implicit conversions
           | 
           | * Non-trivial copy and move constructors
           | 
           | * Template and concept resolution, which can be tricky
           | 
           | * Operator overloads, so that foo[n] or bar() may do
           | something very different than what you expect.
           | 
           | And if you add in macros to the mix, you can really go crazy.
           | For example, in this SO answer:
           | https://stackoverflow.com/a/70328397/1593077 I explain how to
           | implement an infix operator which lets us write:
           | int x = whatever();         if ( x is_one_of {1, 3, 42, 7,
           | 69, 550123} ) {             do_stuff(x);         }
           | 
           | C is simply not that kind of language (well, macros
           | notwithstanding I suppose).
        
           | pjmlp wrote:
           | using namespace my_better_name = my_module;
           | //....              my_better_name::create(); // colision
           | gone
           | 
           | Problem solved, better improve your C++ knowledge.
        
             | Arch-TK wrote:
             | This addresses one (very uninteresting) "problem" shown by
             | the person you are replying to. A problem which can be
             | solved regardless of the existence of "using namespace a =
             | b;" by simply not using the namespace. There are still all
             | the other problems which are more concrete but which
             | namespaces can't solve.
             | 
             | The C standard library should have (maybe as early as C11
             | or C99) picked stdc_ as a "namespace", reserved it, made it
             | a warning to put things in it, and used it for everything
             | going forwards.
             | 
             | What C needs is not namespaces, it's a module system, so
             | that symbol visibility can be more tightly controlled.
        
               | pjmlp wrote:
               | Good luck with that.
        
           | jstimpfle wrote:
           | I agree with everything you say but want to add a caveat to
           | the "long descriptive names". I find that there must be a
           | balance where names are clear and unique enough while not
           | tiring the eyes too much by making them read long boring
           | repetitive prefixes all the time. There is a fine art to
           | creating words and also sentences (statements).
           | 
           | By now if a "module prefix" is bigger than say 4-5
           | characters, I get unhappy and know I need to improve, to find
           | a short mnemnonic. Same goes for local variables names, which
           | I try keep at 1-5, rarely they get 10+ characters long. There
           | is always this tension between names being "self-documenting"
           | and "just long enough to remind of the purpose that was
           | explained in a not too distant context". Variables that are
           | frequently used should be shorter. Variables that have a
           | clear intuitive use (like "x" or "i" which most often have
           | very clear meaning with only little context added) should be
           | shorter. Module names should _always_ be very short because
           | (I assume) there are only few modules, and it's better to
           | remember the purposes of a few modules together with their
           | abbreviated names, than to have to read a long repetitive
           | module name each other line. Function name suffixes (without
           | the module prefix) should often be long because there are
           | many different functions and not all of them can be cached
           | with their meanings in the programmer brain - so I allow
           | function names to be a little self-documenting typically.
           | 
           | I agree that the names in POSIX and C are too short and
           | cryptic but think they made more sense in the context of
           | Unixes of the 1970s when projects weren't as big as today.
           | 
           | I still try to stay within 80 or 100 columns because line
           | length a readability / eye strain concern as well, but given
           | that I'm in the 8 spaces camp I don't freak out anymore if
           | there is the occasional 140 characters line and I'm to lazy
           | to trim it down. Judicious insertion of linebreaks is most
           | often useless busywork. Then again, some function signatures
           | take too many arguments
           | (glVertexAttribPointer/glBufferData... or the Win32 API come
           | to mind) and inserting linebreaks in calls can improve
           | readability sometimes.
        
             | AlbertoGP wrote:
             | > "I find that there must be a balance where names are
             | clear and unique enough while not tiring the eyes too much
             | by making them read long boring repetitive prefixes all the
             | time."
             | 
             | I have that problem, repetitive prefixes getting in the way
             | of reading the code, and I've been doing some experiments
             | with what could be described as "local namespacing", only
             | inside an expression or statement, with the prefix/suffix
             | feature of the backstitch macro in my C preprocessor:
             | 
             | https://sentido-
             | labs.com/en/library/cedro/202106171400/#back...
             | 
             | For instance, writing a program with libxml2, I write:
             | Next(reader) @xmlTextReader...;
             | 
             | which gets translated to
             | xmlTextReaderNext(reader);
             | 
             | and I find the first easier to read.
             | 
             | Whether that's the case for others, I don't know.
             | 
             | A longer example from the link I wrote above, this time for
             | libuv:                   @uv_thread_...             t
             | hare_id,             t tortoise_id,
             | create(&hare_id, hare, &tracklen),
             | create(&tortoise_id, tortoise, &tracklen),
             | join(&hare_id),             join(&tortoise_id);
             | 
             | Result:                   uv_thread_t hare_id;
             | uv_thread_t tortoise_id;         uv_thread_create(&hare_id,
             | hare, &tracklen);         uv_thread_create(&tortoise_id,
             | tortoise, &tracklen);
             | uv_thread_join(&hare_id);
             | uv_thread_join(&tortoise_id);
        
           | Calavar wrote:
           | > Yes, it can save some typing, but programing is never hard
           | because of typing, programing is hard because its hard to
           | understand what is going on.
           | 
           | I agree that optimizing number of keystrokes is a bad goal,
           | but in my opinion that isn't the selling point of namespaces.
           | The benefit is better readability. Code written with
           | namespaces lets your eye focus the semantic name of the
           | method, not extra character noise that was added as a form of
           | manual name-mangling.
           | 
           | This is highly opinionated, of course, because there is a
           | tradeoff with the ability to uniquely identify a method at a
           | glance, as you mentioned. Which camp you fall into is likely
           | to depend on what sort of software you write and whether you
           | use an IDE or a text editor.
        
       | Kukumber wrote:
       | They really want to kill C right?
       | 
       | If you want a better C, do yourself a favor and use D
       | 
       | Same syntax, more secure and with modules
        
         | Xunjin wrote:
         | This kind of comment is not helpful at all, I use Rust today
         | however, yet I cannot sometimes deny, it's better to use C
         | depending on the target and restrictions by hardware compilers.
         | 
         | Every language has downsides/upsides and time. There is a nice
         | quote by Titus Winters: "Engineering is programming integrated
         | over time"[0], this helped me a lot in learning that context
         | matter for tools/languages/frameworks, and you can learn with
         | their mistakes.
         | 
         | [0]: https://youtu.be/tISy7EJQPzI
        
         | Aardwolf wrote:
         | Which change in particular do you dislike?
        
           | Kukumber wrote:
           | It's not about things i dislike, it's subjective, it is about
           | their priority and the feature they think are needed
           | 
           | I'm still waiting for tagged unions, tuple and pattern
           | matching
           | 
           | auto and #embed is nice to have though
        
             | __phantomderp wrote:
             | I don't think we have any proposals for language-supported
             | tagged unions (have to do them yourself), tuples (write
             | your own macro/struct), or pattern matching (lole).
             | 
             | You'll have a hard time pushing for those: C will likely
             | never get them in those forms. At least, not for another
             | 10-20 years, since there's no existing C compiler that
             | implements any of those things yet.
        
               | Kukumber wrote:
               | There you highlighted what i meant by they want C dead
        
       | Bigpet wrote:
       | I'm just happy that both C and C++ now have bit stuff like
       | byteswap, popcount, rolr, etc.
       | 
       | It was a bit silly that stuff like that wasn't in either standard
       | until this recently.
        
         | quelsolaar wrote:
         | These this are much more complicated to define then you think
         | when you consider the kinds of strange hardware C supports,
         | where bytes are not 8 bits and things like that. We tried to
         | get endian-ness conversion functions in to c23 but ran out of
         | time just because defining these functions so that their
         | functionality is clear on non standard hardware is so
         | difficult.
        
           | Bigpet wrote:
           | oh, I didn't see that byteswap wasn't in there. I googled the
           | "stdc_" part and saw a paper that included byteswap. I guess
           | that was an older draft.
           | 
           | Damn, so close. Well I hope it's included next time.
        
             | __phantomderp wrote:
             | I'll do my best to get ror/rol and byteswap in!
        
           | fanf2 wrote:
           | It would have been reasonable to define the functions only
           | for the CHAR_BIT == 8 case, and if any implementers that
           | support weird architectures want the functionality let them
           | come forward with a proposal. I got the impression that the
           | people working on the proposal got into an avoidable mess of
           | complications.
        
             | quelsolaar wrote:
             | Given that weird platforms are so dependent on C and since
             | we release versions so infrequent, we try to get it right
             | the first time. Often even defining what is excluded
             | becomes complicated.
        
             | cbrogrammer wrote:
             | if C throws away portability it is no longer C
        
               | pjmlp wrote:
               | C's secret to portability is called #ifdef spaghetti and
               | mostrosities like autoconf.
        
       | TheBrokenRail wrote:
       | I am so glad to see #embed.
       | 
       | I always thought it was silly that the best way to embed a
       | resource in a binary using CMake was to either convert it to
       | hexadecimal C array manually or use specific flags with ld to
       | convert it into an object (and good luck getting that to work
       | reliably with cross-compiling!).
        
       | flomo wrote:
       | I really don't know any C beyond K&R years ago, but this was an
       | enjoyable read.
        
         | tialaramex wrote:
         | Probably worth clarifying whether you actually mean the Second
         | Edition of Kernighan and Ritchie's book "The C Programming
         | Language" which is about ANSI C and thus approximately ISO C89,
         | or you literally meant what is called "K&R C" the language
         | described in the 1978 edition, by the same name, which is a
         | language somehow composed entirely of sharp edges.
        
           | convolvatron wrote:
           | and cute post/pre increment/decrement tricks!
        
             | hbn wrote:
             | I started reading through/doing the exercises earlier this
             | year and I noticed there was a lot of "cute" tricks in
             | their code examples, such as loops that perform
             | functionality that's all defined in the control flow
             | definition (init/condition/increment) of a for loop, and
             | instead of a loop body it's immediately followed by a
             | semicolon.
             | 
             | Perhaps they're just to give you a better understanding of
             | the more minor details/intricacies of the language, but I
             | couldn't help but think with a lot of their code, if I saw
             | a coworker write something like that I'd roll my eyes that
             | they didn't do it in a more obvious way.
        
       | macintux wrote:
       | Extensive discussion about #embed a couple of weeks ago.
       | 
       | https://news.ycombinator.com/item?id=32201951
        
       | JonChesterfield wrote:
       | I'm pleased by making types with the same tag and fields
       | compatible within a TU. That was a stupid thing to work around
       | with offsetof.
       | 
       | Unfortunate characterisation of constexpr in C++ over revisions
       | as feature creep. It's more that any C++ should be executable at
       | compile time with constexpr ultimately going the way of the
       | register keyword, but selling people on things like compile file
       | I/O is a long game.
       | 
       | Ultimately though it's hard to be too excited. I don't have a use
       | case for C any more, beyond keeping some old code alive (with
       | fstrict-aliasing et al and a sense that I really should port it
       | to something else before I can no longer get any compiler to
       | build a program out of it).
        
       | AlbertoGP wrote:
       | I added one of those features, #embed, to my Cedro C source code
       | preprocessor and posted about it yesterday:
       | https://news.ycombinator.com/item?id=32341326
       | 
       | Here is the direct link: "Use #embed from C23 today with the
       | Cedro pre-processor" https://sentido-
       | labs.com/en/library/cedro/202106171400/use-e...
       | 
       | As I write there, "The advantage of using Cedro is that the
       | source code is the same and this way it is very easy to use
       | different compilers, only adding or removing the #pragma line."
       | 
       | I also added a command-line option to embed the bytes as strings
       | instead of byte literals; embedding an 8 MB file, the strings
       | variant compiled between 28 and 72 times faster depending on
       | which compiler I used, gcc or clang. It is more efficient, but
       | less compatible, that's why it's optional.
       | 
       | I'm considering adding some low-hanging fruit, like the number
       | literal separators: 1'384'849 - 1384849
        
         | kibwen wrote:
         | For number literal separators, I'd suggest using underscore
         | rather than apostrophe. Ada, C#, D, Haskell, Java, Kotlin,
         | OCaml, Perl, Python, PHP, Ruby, Go, Rust, Julia, and Swift all
         | use the underscore. C++ wanted to use the underscore, but was
         | forced to use the apostrophe because of user-defined literals.
        
           | AlbertoGP wrote:
           | Yes, I meant implementing it as specified for C23 which was
           | done for compatibility with C++, although I do prefer the
           | underscores.
           | 
           | Maybe that's a better idea, implement it with underscores in
           | Cedro as it does not need C++ compatibility.
           | 
           | Edit: alright, it works. I also extended the parser to accept
           | the apostrophe as part of number tokens. The only thing it
           | does is to remove the underscores when writing.
           | 
           | I might need another command line option to specify whether
           | the output should be C23:                   1_234_567 -
           | 1'234'567
           | 
           | or pre-C23:                   1_234_567 - 1234567
           | 1'234'567 - 1234567
        
       | EdSchouten wrote:
       | > N3020 - Qualifier-preserving Standard Functions
       | 
       | I just read the actual proposal on it. They describe an example
       | implementation using macros and _Generic.
       | 
       | What's really bad about this is that the arguments provided to
       | these methods are used more than once in the macro body. This
       | will cause:
       | 
       | - gibberish compiler errors in case of typos in the arguments,
       | 
       | - worse compilation times, as the compiler has to process more
       | code.
       | 
       | Constructs like these may lead to an exponential explosion in
       | preprocessed source code size. I hope that nobody implements it
       | that way.
        
         | jsmith45 wrote:
         | The compiler does not need to implement it with the macros
         | shown. They could use other techniques, so long as it has the
         | same net result of preserving constness of the argument. For
         | example, the macro definitions could easily use a macro that
         | resolves to a magic generic intrinsic (which becomes a call the
         | existing function implementation) that maintains the constness
         | of the argument.
         | 
         | An exponential code size increase even with the example
         | implementation would only happen if people are nesting these
         | functions inside each other. And while that could happen, it
         | would be unusual to nest them very deeply. So for compilers
         | that just want an easy implementation, and don't really care
         | about quality of error messages or maximizing compile speed,
         | the shown approach is probably fine.
        
         | jstimpfle wrote:
         | I agree. While I didn't make an effort to actually understand
         | the approach here, N3020 was the one that I disliked because it
         | solves too small of an issue in practice and there probably
         | can't be a simple enough solution. Any solution is probably
         | complicated because the issue is only a symptom really.
         | 
         | The real issue, in my eyes is, that "const" simply doesn't
         | transfer well across function call boundaries. "const" is
         | always only a local judgement - data that is const for one
         | function is not necessarily const for another function. I've
         | even read an old conversation where Dennis Ritchie was uttering
         | concerns about const when it was initially designed.
         | 
         | Typically I use the const keyword only for global static data,
         | because there it makes a real technical difference. Sometimes I
         | declare function pointer parameters as pointer-to-const, partly
         | for reasons of documentation, but I'm always aware of the
         | strstr() issue.
        
         | JonChesterfield wrote:
         | _Generic is an awful feature relative to
         | attribute((overloadable)) anyway so that's probably a non-issue
        
         | colonwqbang wrote:
         | I've encountered this problem in real code bases when you have
         | a getter function for a complex data structure. You have to
         | decide between:                  field
         | *my_struct_get_field(my_struct *s)         const field
         | *my_struct_get_field(const my_struct *s)
         | 
         | In practice you will choose the first instance, so you simply
         | won't use const where you could. Which is unfortunate.
         | 
         | It would be nice to have a shorthand for this. The nested macro
         | shown in the paper looks like it can fix the problem, but is
         | quite complicated. I would like something like:
         | autoconst field *my_struct_get_field(autoconst my_struct *s)
         | 
         | The returned pointer inherits the qualifiers of the argument.
         | Inside the getter function the pointer is const, and there is
         | only one implementation of the function.
        
         | jonathrg wrote:
         | How exactly can this lead to exponential explosion?
        
           | pdw wrote:
           | Code that does things like strcat(strcat(strcat(strcpy(...),
           | ...), ...), ...)
        
             | jonathrg wrote:
             | If I understand the implementation section of the proposal
             | correctly, the preprocessor will just add ome cast to
             | (const char*) per function call (assuming the innermost
             | argument is const). Nothing exponential
        
               | lifthrasiir wrote:
               | For the ease of discussion here is the relevant piece of
               | code:                   #define IS_POINTER_CONST(P)
               | _Generic(1 ? (P) : (void *)(P)  \             , void
               | const *: 1                                       \
               | , default : 0)         #define STATIC_IF(P, T, E)
               | _Generic (&(char [!!(P) + 1]) {0}  \             , char
               | (*) [2] : T                                        \
               | , char (*) [1] : E)         #define _STRING_SEARCH_QP(T,
               | F, S, ...)       \             STATIC_IF
               | (IS_POINTER_CONST ((S))         \                 , (T
               | const *) (F) ((S), __VA_ARGS__)  \                 , (T
               | *) (F) ((S), __VA_ARGS__))              #define memchr(S,
               | C, N) _STRING_SEARCH_QP(void, memchr, (S), (C), (N))
               | #define strchr(S, C) _STRING_SEARCH_QP(char, strchr, (S),
               | (C))         #define strpbrk(S1, S2)
               | _STRING_SEARCH_QP(char, strpbrk, (S1), (S2))
               | #define strrchr(S, C) _STRING_SEARCH_QP(char, strrchr,
               | (S), (C))         #define strstr(S1, S2)
               | _STRING_SEARCH_QP(char, strstr, (S1), (S2))
               | 
               | Here `IS_POINTER_COST` expands `P` twice so
               | `_STRING_SEARCH_QP` expands `T`, `F`, `S` and each
               | variadic argument 2, 2, 4 and 2 times respectively. Note
               | that only `S` and variadic arguments are expressions and
               | _evaluated_ only once, but the number of _expanded_
               | tokens can blow up exponentially.
        
       | teddyh wrote:
       | What will the __STDC_VERSION__ be?
        
         | jwilk wrote:
         | The current draft says 202311L. But I guess it's not set is
         | stone yet?
        
       | hgs3 wrote:
       | > N3042 - Introduce the nullptr constant
       | 
       | They should have formally defined NULL as void* instead.
       | 
       | > Someone recently challenged me, however: they said this change
       | is not necessary and bollocks, and we should simply force
       | everyone to define NULL to be void*.
       | 
       | I'm glad someone brought it up. The scenarios nullptr "fixes" are
       | so minor adding a new null concept to fix them is the sort of
       | overkill solution I expect from the C++ committee, not the C
       | committee. It's a shame this wasn't thought-out more.
       | 
       | > I said that if they'd like that, then they should go to those
       | vendors themselves and ask them to change and see how it goes.
       | 
       | The C committee has already broken compatibility on numerous
       | occasions: forcing 2's complement representation, changing the
       | type of u8"" string literals from char to unsigned char, removing
       | mixed wide string literal concatenation (allowed in C11,
       | disallowed in C23). It's difficult to be convinced by the
       | argument of backwards compatibility when the committee has
       | already broken it on multiple occasions. Furthermore, I seriously
       | doubt compiler vendors who define NULL as 0 would take issue with
       | redefining it as void*. This change is so minor and
       | inconsequential there should be no push back.
        
         | stonemetal12 wrote:
         | I am not all that standards savvy, so pardon my ignorance.
         | 
         | >Introduce the nullptr constant
         | 
         | >They should have formally defined NULL as void* instead.
         | 
         | That doesn't make any sense to me. void* is a type isn't it?
         | nullptr is a value. I don't see the overlap.
        
           | hgs3 wrote:
           | > That doesn't make any sense to me. void* is a type isn't
           | it? nullptr is a value. I don't see the overlap
           | 
           | The C standard permits NULL to be defined as (void*)0 or 0.
           | The issue is this creates an inconstancy when NULL is used
           | with _Generic selection: a compiler defining NULL as (void*)0
           | will cause NULL to be caught by a pointer case whereas a
           | compiler defining NULL as 0 will cause it to be caught by an
           | integer case. This inconsistently could be solved by
           | mandating NULL be defined as (void*)0 so it will always be
           | caught by the pointer case.
           | 
           | The issue is instead of mandating (void*)0 the C committee
           | decided to introduce a whole new nullptr concept. This
           | duplicates the concept of null in the language and requires
           | all C compiler vendors to modify the languages type system to
           | account for it. So it creates more work for vendors and
           | duplicates the concept of null all to solve this minor use
           | case.
        
             | [deleted]
        
       | rwmj wrote:
       | It's a shame that attribute((cleanup)) wasn't standardized since
       | it's very widely used in real C code, but at least the badly
       | designed and massively overengineered "defer" suggestion was
       | rejected.
        
         | pjmlp wrote:
         | Widely used in GCC C code.
        
           | rwmj wrote:
           | It's supported by Clang, so widely used in open source
           | projects - systemd, libvirt, everything using glib, some
           | Linux kernel tools, dracut, qemu, glibc, ..
        
             | pjmlp wrote:
             | Apparently, no one has bothered to update clang's
             | documentation on that regard, https://releases.llvm.org/14.
             | 0.0/tools/clang/docs/AttributeR...
             | 
             | Many of those open source projects happen to only compile
             | with GCC, exactly because they rely on GCC C.
             | 
             | Google has spent several man years making the Linux kernel
             | compilable with clang, and not all of it has reached
             | upstream to this day.
        
               | loeg wrote:
               | Yes, Clang is pretty bad about updating their
               | documentation of GCC features they support.
        
               | ndesaulniers wrote:
               | > and not all of it has reached upstream to this day.
               | 
               | Which parts haven't? Asking as the project lead.
        
               | rwmj wrote:
               | https://clang.llvm.org/docs/AttributeReference.html#clean
               | up. We use it in several projects and compile those with
               | clang.
               | 
               | Here's nbdkit compiled with clang 14:
               | 
               | https://gitlab.com/nbdkit/nbdkit/-/jobs/2814022460
               | checking whether clang is Clang... yes       [...]
               | checking if __attribute__((cleanup(...))) works with this
               | compiler... yes
        
               | pjmlp wrote:
               | Thanks for the example.
        
       | Aissen wrote:
       | > memset_explicit
       | 
       | Exciting to see this in the standard. Once this is in all
       | compilers, this will probably have security effects beyond C.
       | 
       | Fun fact: Miguel Ojeda linked to @cperciva's article and its
       | following HN discussion in the note (N2897) to propose this:
       | https://news.ycombinator.com/item?id=8270136
        
         | Sakos wrote:
         | I'm not familiar with C at all, but I'm wondering why they
         | chose to add a keyword just for this specific case instead of
         | providing a way to granularly tell the compiler not to optimize
         | specific lines of code or what optimizations are permitted.
        
           | mtlmtlmtlmtl wrote:
           | This is mainly useful for implementing things like
           | explicit_bzero(), to guarantee memory is zeroed before
           | freeing it(free() makes no guarantees about the contents of
           | freed memory). This is hugely important for discarding things
           | like private keys.
           | 
           | Up until now, safely implementing explicit_bzero() has
           | depended on various hacks that a new compiler version might
           | drag out from under you. This should end the "arms race".
        
           | Aissen wrote:
           | I'm guessing that this would have been either too complex, or
           | taken too many years. The pragmatic, lower scope approach,
           | already required quite a lot of work.
        
           | Jensson wrote:
           | Volatile is exactly for this case. If you write or read a
           | volatile variable then the compiler isn't allowed to remove
           | the operations for any reason.
        
             | 1ris wrote:
             | That's what i thought, but apparently this is wrong.
             | 
             | Had a disscussion about this here:
             | 
             | https://news.ycombinator.com/item?id=27804810
        
               | Jensson wrote:
               | I don't believe they are correct, or at least their
               | arguments doesn't explain why it wouldn't work. Volatile
               | wouldn't work with memset since memset doesn't take a
               | pointer to volatile memory, that is true, but you could
               | set the volatile data yourself and it shouldn't be
               | optimized away as per the spec. The data living on a
               | stack doesn't matter, it works perfectly fine having
               | volatile data on the stack.
               | 
               | I tried the following code in a few compilers, it works
               | perfectly for overwriting buffer pointers that goes out
               | of scope, also tested removing the volatile to ensure
               | that the memory write was ignored and I reproduced the
               | code vulnerability where the previous message was still
               | there the second time I tried to read a message, and from
               | the way I read the spec this shouldn't be optimized away
               | by any compiler:                   void
               | memset_explicit(char* p, char c, size_t n) {
               | volatile char* vp = p;             while (n--)  *vp++ =
               | c;         }
               | 
               | Casting other data types to a pointer to volatile data is
               | within the spec, and the compiler should then treat it
               | like any other volatile data.
        
               | [deleted]
        
             | aidenn0 wrote:
             | It's a bit more subtle than that; variables aren't
             | volatile, objects are volatile. A volatile pointer is a
             | hint to the compiler that the object pointed to might be
             | volatile. If it can't disprove that "might" into
             | "definitely not" then it treats it as volatile.
        
         | dataflow wrote:
         | I'm confused, why didn't they just mandate memset_s instead of
         | introducing memset_explicit?
        
           | loeg wrote:
           | Because Annex K sucks and should be removed entirely.
        
             | mst wrote:
             | For those, like me, who weren't up to speed with why this
             | argument is made, https://www.open-
             | std.org/jtc1/sc22/wg14/www/docs/n1967.htm seems to be a
             | good summary of the problems with it.
             | 
             | (if it isn't, hopefully somebody who knows more will post a
             | better link)
        
           | Aissen wrote:
           | My understanding, following the reading of https://www.open-
           | std.org/jtc1/sc22/wg14/www/docs/n2897.htm is that it was
           | simpler to "salvage" a single function from "Annex K" by
           | renaming it, considering that "Annex K" was both optional and
           | controversial (some called to remove it).
        
           | rurban wrote:
           | Because people don't care abut security (or they just have no
           | idea), hence they roll their own _FORTIFY_SOURCE hacks, but
           | they still don't get memset_s right. So they rather named it
           | what it is, protect from compiler optimizations, but not from
           | cache side-channel attacks, which the _s variant would do.
           | Because that is the secure variant.
           | 
           | memset_explicit is just to overcome stubborn optimizer people
           | who insist to optimize functions away (without warnings!),
           | even when they have no idea about side-effects.
        
             | dataflow wrote:
             | > but not from cache side-channel attacks, which the _s
             | variant would do
             | 
             | Wow, was that a requirement for memset_s? I'd never heard
             | of timing being a something guaranteed by the *_s
             | functions, regardless of security being in their name.
        
       | qalmakka wrote:
       | What saddens me is that this is yet another missed opportunity
       | for fixing the C strings library, which is sadly mostly broken
       | beyond repair or straight unusable on modern systems.
       | 
       | Annex K tanking badly was just sad. At least they finally
       | standardized strdup().
        
         | fanf2 wrote:
         | Annex K is appalling. It depends on global shared mutable
         | state, so it is incompatible with threading.
        
         | rhexs wrote:
         | The committee is still run by fossils carrying the misguided
         | torch of endless backwards compatibility. No one is compiling
         | 1980s C on GCC trunk releases, but by god we still need to make
         | sure it compiles cleanly.
        
         | jstimpfle wrote:
         | How do you intend to fix the strings library? beyond strlen()
         | and _maybe_ the search functions like strchr() there really
         | isn't much you should use. And those functions are _fine_.
         | 
         | Some of the string functions, like strtok() or strcat(), and I
         | would also include strdup(), are broken beyond repair and
         | should simply not be used. The only way to "fix" the library
         | would be to remove these functions, but I assume this would
         | create more trouble with legacy code than it's worth.
         | 
         | C strings are mostly a useful storage format for small strings,
         | including string literals, and as a "default" string type for
         | the little basic functionality that is expected to be part of C
         | to support a small program growing up - including printf() and
         | friends.
         | 
         | For longer strings, you are expected to build datatypes that
         | fit your usecase, and that includes decisions about memory
         | management. It should not be the responsibility of the language
         | to make these decisions for you.
        
           | pjmlp wrote:
           | By having something like SDS[0] as part of the standard
           | library, but that is clearly asking too much.
           | 
           | [0] - https://github.com/antirez/sds
        
             | dataflow wrote:
             | This is BSTR for the Windows/COM folks.
        
           | fullstop wrote:
           | I get strtok and strcat, but what's wrong with strdup?
        
             | jstimpfle wrote:
             | It's only very marginally useful, strdup(x) is equivalent
             | to malloc(strlen(x)). It is one of the few functions that
             | allocate, but it doesn't have "alloc" in its name, putting
             | more of an onus on the programmer to free() appropriately.
             | Also, it's advocating bad programming style. Just mallocing
             | random small things all over the place is not how you
             | structure programs well. (Instead, most strings should be
             | in a pool or in another context that is more local than the
             | malloc() heap, so individual strings can be released at
             | once without having to track all the strings attached to a
             | million different structures).
        
               | fullstop wrote:
               | Other POSIX functions, such as glob, also allocate memory
               | and don't have alloc in the name.
               | 
               | It's something that I use. Maybe not frequently, but I
               | definitely use it.
               | 
               | Also, it is in no way equivalent to malloc(strlen(x)).
               | You're off by one byte and you've not copied any of the
               | string.
        
               | jstimpfle wrote:
               | Oops, see how my mind became lazy when thinking about
               | such a boring, tasteless function? A function like
               | memdup() would be slightly more useful.
               | 
               | At least, glob() has its own globfree() function, leaving
               | room for slightly more mindful organization.
        
               | fullstop wrote:
               | You can't even do strcpy(malloc(strlen(x)+1), x) because
               | it doesn't check that malloc succeeded. strdup does all
               | of this for you! :-)
        
       | rando14775 wrote:
       | When will they add a slice (fat pointer) type? I.e. a generic
       | version of
       | 
       | > struct T_slice { T* ptr; size_t len; };
       | 
       | with all the syntactic sugar and bounds checks added in. This
       | could catch so many out of bounds issues and doesn't seem that
       | hard to do.
        
         | axus wrote:
         | The author listed "wide pointers" in the second paragraph of
         | things they are impatient for, and would like a faster release
         | cycle for. It sounds like it could be another 10 years.
        
           | __phantomderp wrote:
           | The original authors of the language also advocated for this,
           | and Walter Bright's D compiler has a BetterC compiler that
           | also includes a
           | 
           | int arr[..];
           | 
           | syntax for such things. But, our plates are full and the
           | proposal wasn't written: it'll be up for next release, for
           | sure, and it's one of the many things I would like to put in
           | the language.
        
       | teddyh wrote:
       | > _N2888 - Exact-width Integer Types May Exceed (u)intmax_t_
       | 
       | This creates a problem. How do you _portably_ printf() an integer
       | type that you don't (and can't) know the size of, like, say, a
       | _uid_t_?
       | 
       | Before, when _intmax_t_ was guaranteed to be the largest type,
       | you could reliably and portably cast a _uid_t userid_ to an
       | _intmax_t_ and _printf()_ that: printf(PRIdMAX  "\n",
       | (intmax_t)userid);
       | 
       | But now, when any type might be larger than an _intmax_t_? What
       | do you do?
       | 
       | (The same problem exists when reading an integer string; how do
       | you read, say, a port number, when you don't know what type
       | _in_port_t_ is? Previously, you could call _strtoimax()_ on the
       | string, detect overflow, and then cast the resulting _intmax_t_
       | to _in_port_t_ and finally compare for equality to make sure it
       | wasn't truncated.)
       | 
       | > _There is active work in this area to allow us to transition to
       | a better ABI and let these two types live up to their promises_
       | 
       | Yes please. Introducing new wider integer types in your CPU, but
       | pretending you haven't created a new architecture with an
       | associated new ABI, is silly.
        
         | Aardwolf wrote:
         | Maybe C just needs to provide a way to print/format any built
         | in type without specifying it. They got stdc_popcount to work
         | with any integer type (looks like function overloading in C
         | through macros), so can same be done for printing integers?
        
           | teddyh wrote:
           | Don't forget the other side of the coin; we also need to read
           | a string into any integer type (while also detecting
           | overflows).
        
         | formerly_proven wrote:
         | > But now, when any type might be larger than an uintmax_t?
         | What do you do?
         | 
         | Complain to POSIX so they add printf macro defines for all
         | their implementation-sized integer types by the year 2100.
        
           | teddyh wrote:
           | For _printf()_ , that would work. For _sscanf()_ , POSIX
           | could add macros there, too, but _sscanf()_ _does not detect
           | integer overflow_. That's why the only previous solution was
           | _strtoimax()_ which _does_ detect overflow.
        
             | rwmj wrote:
             | scanf not detecting integer overflow is arguably another
             | problem. It's usually the easiest way to parse numbers out
             | of simple strings, but the lack of overflow detection is a
             | problem. (Actually we use sscanf for this and ignore the
             | overflow problem, as long as it doesn't cause any security
             | or correctness concern.) IIRC glibc actually does overflow
             | detection, so there's not anything inherently problematic
             | with adding it.
        
         | orlp wrote:
         | To the first problem you can use a classic mod 10 divide by 10
         | loop to convert into a string digit by digit.
         | 
         | Similarly for the second problem you can read digit by digit in
         | reverse order and create the number using x = 10x + d while
         | detecting overflow.
         | 
         | I'm not saying that this is a great thing to need to do, but
         | the mentioned problems aren't particularly fundamental and the
         | solutions are CS101 stuff.
        
           | teddyh wrote:
           | You may be right that it's the best solution. It's horrible
           | to contemplate, but it might be so.
        
             | jcranmer wrote:
             | The best solution is to say that uid_t et al are probably
             | no larger than uint64_t, and introducing a static_assert
             | that makes sure that the compiler will fail if it's not.
             | 
             | Sure, someone might come up with an architecture where
             | that's not true. But even in an intmax_t-is-really-the-
             | largest-integer-type world [which hasn't been true for the
             | major C compilers for the past decade or so!], I can almost
             | assuredly come up with some compilation modes that still
             | conform with whichever C version you use that still causes
             | your code to fail horribly. I don't think there's any
             | architecture where uid_t et al would exceed uint64_t, and
             | until such an architecture comes to exist, it's quite
             | frankly not worth caring about.
        
             | orlp wrote:
             | Oh I agree, it's bad.
        
           | csmpltn wrote:
           | > "To the first problem you can use a classic mod 10 divide
           | by 10 loop to convert into a string digit by digit."
           | 
           | But the "physical" size of the number as represented in
           | memory can change across platforms, no?
        
         | __phantomderp wrote:
         | I mentioned this in another article of mine. Robert Seacord
         | closed the loop on "how do I print numbers the non-shit way?"
         | in C23 with N2680:
         | 
         | https://thephd.dev/c-the-improvements-june-september-virtual...
         | 
         | https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2680.pdf
        
           | teddyh wrote:
           | Great, thanks! But that only solves _printing_. How do I do
           | the _reverse_ operation? I.e. parsing a string to an integer
           | type of an unknown size, while checking for overflow?
        
             | __phantomderp wrote:
             | -\\_(tsu)_/-
             | 
             | Someone's got to write a paper to get it in, hopefully
             | based on some sort of existing practice.
        
             | Someone wrote:
             | I think this would work: first, determine the range. For
             | unsigned variants, we have _whatever_max = (whatever)(-1)_
             | , if it isn't already defined.
             | 
             | That makes parsing the unsigned variant relatively simple.
             | Undefined behavior would be end-of-game, but unsigned
             | addition and multiplication will not invoke that (if you
             | need he,p, look at your favorite multiple-precision library
             | for ideas about detecting wraparound)
             | 
             | For parsing signed integers, derive the min and max values
             | of the signed variant from the max value of the unsigned
             | one ( _(whatever_max - 1) / 2_ or something like that
             | 
             | Then parse your input to the unsigned variant first, then
             | check whether it fits in the unsigned version.
        
         | pdw wrote:
         | I think it's best to regard intmax_t as a failed experiment.
         | E.g. GCC has long supported __int128, clang has _ExtInt, other
         | compilers have similar extensions, but they refuse to increase
         | intmax_t due to ABI concerns.
        
           | kazinator wrote:
           | intmax_t can rationally be regarded as being outside of the
           | ABI concept. It should give you the maximum integer
           | available, not the maximum integer that your compiler had 15
           | years ago for the sake of compatibility.
           | 
           | Anyone who uses intmax_t in a durable API (public function
           | argument, member of public structure) is just a goof.
        
             | dodobirdlord wrote:
             | There are a lot of goofs out there running goofy binaries
             | for which they have lost the goofy source code. These
             | people are unfortunately also the committee's problem.
        
             | eropple wrote:
             | Lotta goofs write a lotta code, though. I'm not sure that I
             | disagree with you, but this feels like the sort of thing
             | that could _really_ hurt to break.
        
             | teddyh wrote:
             | > _It should give you the maximum integer available_
             | 
             | How would you compile that to machine code which would,
             | when run, still give you the maximum integer on next year's
             | CPU with all-new super-duper wide integers?
             | 
             | It is more reasonable to think of a binary to be compiled
             | to a specific architecture triplet, and if the CPU changes,
             | the architecture must change, and therefore the ABI, and if
             | the OS wants to run a binary from an older architecture,
             | some emulation layer is needed.
             | 
             | Of course, this would be a lot of work for the operating
             | system people if they want to run binaries from lots of
             | what are now different architectures, and it was apparently
             | easier to just force the C standard to abandon the entire
             | concept of _intmax_t_ being the largest.
        
               | dianeb wrote:
               | In C, you get what you pay for. When the platform
               | changes, it's not unreasonable to recompile in order to
               | take advantage of new hardware features. The OS is
               | certainly different, even if it's still called "Whatever
               | OS", it had to be recompiled in order to support the new
               | hardware, unless, of course, it doesn't. We're all lazy
               | when it comes to our own work, whether it's supporting
               | new hardware in a compiler or OS or super web service on
               | some new cloud platform or a large system vendor.
               | 
               | intmax_t by intent is the maximum integer available on a
               | given platform. Abandoning that idea is a disservice to
               | compiler writers, programmers -- especially scientific
               | programmers -- everywhere.
        
               | teddyh wrote:
               | Oh, I agree completely. Distributions should have bit the
               | bullet and announced a new architecture when the new CPUs
               | with new integer types were released.
        
           | teddyh wrote:
           | OK, fine, but how, then, can the two problems I described be
           | solved?
        
             | pdw wrote:
             | Well, what did you do before intmax_t was added to C99?
             | 
             | - POSIX defines that in_port_t is equal to uint16_t, so for
             | that type there's no problem at all ;)
             | 
             | - Don't use printf/scanf for this, roll your own
             | 
             | - Refuse to print/read uid_t that are outside intmax_t
             | range
             | 
             | - Use an autoconf test
        
               | teddyh wrote:
               | My program was first written when _intmax_t_ already
               | existed; AFAICT, _intmax_t_ was introduced in C99. I also
               | need to parse and /or print _uid_d_ , _gid_t_ , and
               | _pid_t_ , so I guess that in the future I'll have to
               | implement my own integer parser and printer, like user
               | "orlp" suggested elsethread.
               | 
               | Until then I guess I'll have to do something like
               | #if sizeof(pid_t) > sizeof(uintmax_t)       #error Nope
               | #endif
               | 
               | ...repeated for all the types I need to parse and/or
               | print.
        
               | ynik wrote:
               | You can't use sizeof in #if, but you can use C11
               | static_assert instead.
               | 
               | Also this doesn't just apply "in the future" -- integer
               | types larger than intmax_t already exist, C23 just
               | updates the standard to match reality.
        
               | kazinator wrote:
               | You can do this:                 #if SIZEOF_PID_T >
               | SIZEOF_UINTMAX_T       ...       #endif
               | 
               | Where you detect these sizes from the toolchain and
               | deposit them as #define constants in some "config.h"
               | header.
               | 
               | There are ways to detect sizes by compiling a source file
               | to an object file, and then analyzing the object file (no
               | execution), so things work under cross-compiling.
               | 
               | I've used a number of tricks over the years, and settled
               | on this one:
               | 
               | https://www.kylheku.com/cgit/txr/tree/configure?h=txr-278
               | #n1...
               | 
               | The basic idea is that we can take a value like
               | sizeof(long), which is a constant, and using nothing but
               | constant arithmetic, we can convert it to the characters
               | " 8". These characters can be placed into a character
               | array where they are delimited by some prefix that we can
               | look for in the compiled object file with some common
               | utilities.
               | 
               | This is quite durable in the sense that it can be
               | reasonably expected to work through a wide variety of
               | object formats.
               | 
               | In that test program, I have a structure with some
               | character arrays. The larger character arrays hold the
               | informative prefixes. The two-byte arrays hold decimal
               | digits for sizes. Two digits go up to 99 bytes so we are
               | safe for a number of years.
               | 
               | The DEC macro calculates the ASCII decimal value of its
               | argument, expanding to a two-byte initializer for a
               | character array:                 #define D(N, Z) ((N) ?
               | (N) + '0' : Z)       #define UD(S) D((S) / 10, ' ')
               | #define LD(S) D((S) % 10, '0')       #define DEC(S) {
               | UD(S), LD(S) }
               | 
               | E.g.                 DEC(42) -> /* the equivalent of */ {
               | '4', '2' }
               | 
               | But actually                 DEC(42) -> { UD(42), LD(42)
               | }               -> { D((42) / 10, ' '), D((42) % 10, '0')
               | }               -> { ((42) / 10) ? ((42 / 10)) + '0' : '
               | ',                    ((42) % 10) ? ((42 % 10)) + '0' :
               | '0' }
               | 
               | The space instead of a leading zero is so that if we pull
               | this into shell arithmetic, it isn't confused for an
               | octal number.
        
               | nrclark wrote:
               | That problem could be a really good application for a
               | _Generic macro, which is another newish language feature
               | in C.
        
             | [deleted]
        
           | qalmakka wrote:
           | If people use intmax_t and then they expect the ABI to stay
           | stable, than they are at fault, not libc, LLVM or GCC for
           | changing it when the actual "max int" changes.
        
             | pdw wrote:
             | But all major C compilers promise that they won't make ABI
             | changes. So I'd rather say that standardizing intmax_t was
             | a mistake.
        
               | kevin_thibedeau wrote:
               | GCC changed long to 64-bits and x86 codebases had to
               | accept that. The base integer types are designed to be
               | variable sized and you code accordingly based on the
               | minimum size guarantee. Assuming types are frozen in time
               | is how you get things like LLP64.
        
               | brigade wrote:
               | No, gcc added support for ABIs that defined long as 8
               | bytes. They didn't _change_ the size of long for any ABI.
        
               | klodolph wrote:
               | Long is 32 bits on x86, 64 bits on x86_64. x86 code bases
               | broke because they made non-portable assumptions.
        
               | kevin_thibedeau wrote:
               | No. It's 32-bits _minimum_. It can be anything larger.
               | That 's what the language says. Assuming otherwise is
               | nonportable.
        
               | [deleted]
        
               | klodolph wrote:
               | Why are you saying "no" and then agreeing with what I
               | wrote? Am I misunderstanding something?
        
               | TylerE wrote:
               | Because "32 bits or greater" != "exactly 32 bits"
        
               | klodolph wrote:
               | Right. One is what the C standard says, and the other is
               | what the ABI standard says. Maybe I'm missing something
               | here.
               | 
               | Obviously, when someone says "type X is size Y on
               | architecture Z", they're not talking about the C
               | standard, they're talking about some particular ABI.
        
               | kevin_thibedeau wrote:
               | Because it isn't "64-bits on x64". It's 32-bit minimum on
               | all platforms. Assuming larger sizes than the minimum is
               | how brittle code gets written. A compiler vendor can set
               | the sizes to whatever they want regardless of the target
               | architecture and claim conformance if they meet the
               | minimum.
        
               | kazinator wrote:
               | If you don't standardize intmax_t, programs will invent
               | the concept for themselves through some combination of
               | preprocessing directives and shell scripts that probe the
               | toolchain.
               | 
               | If you want to know "what is the widest signed integer
               | type available", you will get some kind of answer one way
               | or another.
        
               | mtlmtlmtlmtl wrote:
               | Or even worse, if they don't know their build tools, some
               | kind of union based monstrosity...
        
               | Gibbon1 wrote:
               | The mistake was not properly versioning the ABI
        
             | teddyh wrote:
             | I feel the fault instead lies in the operating system for
             | wanting to run old binaries so much that the operating
             | system pretends that the system architecture (and
             | associated ABI) has not changed, even when there exists
             | larger integers than what the previous architecture had set
             | as _intmax_t_.
        
               | afiori wrote:
               | The problem is not old binaries, it is old code that
               | assumes an upper bound to the size of intmax_t.
        
               | teddyh wrote:
               | I'm not sure what you mean. I agree that the problem is
               | not the binaries, but I believe the problem to be the
               | operating system. The source code may not have assumed a
               | specific upper size limit on _intmax_t_ , but the
               | binaries were compiled in good faith to a specific
               | machine architecture, which had a specific width on
               | _intmax_t_ , namely the largest integer type which that
               | architecture could support. If then new CPU models come
               | out with larger integer types, but the operating systems
               | still runs that same binary straight up (without any
               | emulation layer), this exposes the old binary to larger
               | integer types, breaking the binary. The binary is not at
               | fault, it's the operating system which committed the sin
               | of running the binary on what is effectively a different
               | architecture without any considerations on what
               | architecture the binary was actually compiled for.
        
               | afiori wrote:
               | The OS could do this for old binaries but no for old
               | source. A lot of FOSS code has baked in assumptions about
               | the max size of intmax_t and the compiler and the OS
               | cannot do much about broken logic.
        
               | teddyh wrote:
               | > _A lot of FOSS code has baked in assumptions about the
               | max size of intmax_t_
               | 
               | Well, that code is not, and was not ever, standards
               | compliant.
        
               | shadowgovt wrote:
               | And it turns out that doesn't matter much in terms of
               | adoption and popularity.
               | 
               | After all, if predictable behavior in the future across
               | all possible configurations were a high priority
               | requirement for the tools people use to make computers do
               | things, neither C nor C++ would have gotten off the
               | ground. Instead, people don't memorize the entire
               | standard. They write code that works on their machine,
               | publish it, fix the bugs when people say it doesn't work
               | on their machine, and maybe converge towards something
               | that is standards compliant but, more likely, they
               | converge towards something that works on the standard
               | implementation on 90% of the operating systems available.
        
               | kazinator wrote:
               | > A lot of FOSS code has baked in assumptions about the
               | max size of intmax_t
               | 
               | Would you be able to name three examples?
        
               | afiori wrote:
               | I realize I used FOSS improperly, I had no reason to
               | single it out; my comment reads like a critique of open
               | source which was not my intention.
               | 
               | My intention was to make a good case for old code being
               | compiled today by third parties.
               | 
               | But to my understanding intmax_t was introduced in C99,
               | before widespread dominance of 64 bits platforms. So it
               | would not be surprising that there exists code with lower
               | assumptions about its size.
               | 
               | But actually I made also another mistake, the major
               | problem had little to do with faulty logic and more to do
               | with dynamic linking [0]
               | 
               | I will see myself out today.
               | 
               | [0] https://thephd.dev/intmax_t-hell-c++-c
        
         | csmpltn wrote:
         | > "How do you portably printf() an integer type that you don't
         | (and can't) know the size of"
         | 
         | "Portably" across what? You need to narrow down the problem
         | space and better define it.
         | 
         | If it's portably _across a myriad of processor architectures,
         | hardware profiles and operating systems_ then you would need to
         | take the intermediate representation route (like Java). There
         | 's very little else that can be done in the face of infinite,
         | non-compatible ABIs at every layer.
        
           | teddyh wrote:
           | I mean portable over all platforms which claims to support
           | standard C.
           | 
           | > _you would need to take the intermediate representation
           | route (like Java). There 's very little else that can be done
           | in the face of infinite, non-compatible ABIs at every layer._
           | 
           | No, I don't think so. There's an obvious solution, dictated
           | by the architectual choices made by C (i.e. every
           | architecture which introduces a new larger integer type, by
           | necessity redefines _intmax_t_ , and creates a new ABI), but
           | the community (for reasons of their own) did not want to do
           | it that way, and instead broke the promise of _intmax_t_
           | always being the largest type.
        
       | Sakos wrote:
       | For anybody else who has no idea what C23 is and for whom the
       | article's intro really wasn't helpful, this is about a proposed
       | major standard revision for the C language
       | (https://en.wikipedia.org/wiki/C2x).
        
       ___________________________________________________________________
       (page generated 2022-08-05 23:02 UTC)