[HN Gopher] Clang is better than GCC
       ___________________________________________________________________
        
       Clang is better than GCC
        
       Author : zdw
       Score  : 64 points
       Date   : 2022-11-28 00:25 UTC (4 hours ago)
        
 (HTM) web link (yurichev.org)
 (TXT) w3m dump (yurichev.org)
        
       | blinkingled wrote:
       | https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65253 - this has
       | been open since 2015 - probably should have been prioritized
       | given it finds real world bugs.
       | 
       | GCC is still the king of the hill though - the amount of support
       | for various ISAs, new features like C++20 stuff - it's far from
       | lagging clang in hugely significant ways. (GCC user since 2.95 on
       | Solaris/SPARC!)
        
       | WalterBright wrote:
       | I've learned the hard way that the functions to focus on in a C
       | code review are:
       | 
       | malloc, calloc, realloc, memcpy, memset, memcmp, strcpy, strncpy,
       | and a few others. They're a rich source bugs, usually:
       | 
       | 1. an off-by-one error due to the terminating 0
       | 
       | 2. an overflow vulnerability (particularly with
       | malloc/calloc/realloc)
        
       | nolok wrote:
       | > Yes, venerable GCC is a very important and respected tool. And
       | this is my bug, not GCC's flaw.
       | 
       | > But that bug in my code ruined one month of my work. I coded
       | without realizing tests don't work at all. OUCH!
       | 
       | Sorry to say but that too is your fault. First thing you're
       | supposed to do with tests is make them fail, then make them pass.
       | The only safe way to handle it is that if you've never seen your
       | test as written fail, then it never fails.
       | 
       | As an aside, the first line says:
       | 
       | > Sorry for the provocative title, but I'm too emotional these
       | days.
       | 
       | And this really ticks me off for some reason, probably because
       | the entire web and beyond has moved to that form of expression
       | where one exaggerate and then say "yeah but I have my reason". I
       | miss adult discourse.
        
         | userbinator wrote:
         | I suggest the author of that code try a denser language, one
         | that requires more focus on individual characters, and figure
         | out a more methodical approach to debugging. I saw the error at
         | first glance, but that may just be because the code is in an
         | unusual style (https://news.ycombinator.com/item?id=33768662);
         | suggesting that a second pair of eyes will also help.
        
           | MikePlacid wrote:
           | > that a second pair of eyes will also help.
           | 
           | This code will not pass a code review in my last shop:
           | 
           | 1. First get the return code: rc = memcmp(...
           | 
           | 2. Then compare, and please put the constant first: if( 0!=rc
           | ) ...
           | 
           | C and C++ are dangerous tools, you should not use in
           | production all the "sugar" they provide.
        
         | mlindner wrote:
         | > > Sorry for the provocative title, but I'm too emotional
         | these days.
         | 
         | > And this really ticks me off for some reason, probably
         | because the entire web and beyond has moved to that form of
         | expression where one exaggerate and then say "yeah but I have
         | my reason". I miss adult discourse.
         | 
         | I completely agree. I've seen a lot of people try to excuse
         | being rude to people because they're extra emotional at the
         | moment. If you're extra emotoinal at the moment, *don't write
         | it*. It's more reasonable to use as a post-facto reason.
         | (Example: Sorry. Yesterday I wasn't in my right mind and I said
         | some mean things to you that I regret saying. It won't happen
         | again.) But it's not at all okay to try to excuse what you're
         | about to write. That's not an apology at all. That's like a
         | person with anger management problems trying to excuse being
         | able to hit people whenever they want.
        
         | pizlonator wrote:
         | We all have code that doesn't get tested "the way it's supposed
         | to be" and then waste a bunch of time trying to figure out wtf
         | happened.
         | 
         | And clang is really great at catching more issues than other
         | compilers would, and then reporting them better than the
         | others. So the title isn't far off.
        
           | nolok wrote:
           | I firmly agree with your second paragraph and the general
           | point the link made about error message being vastly superior
           | in clang.
           | 
           | I don't strictly disagree with your first point, but I
           | disagree with the attitude from many dev regarding tests that
           | lead to it becoming too common like in this article (and I'm
           | not piling on the guy, it's very very common).
           | 
           | It's like people have forgotten what tests are for and thus
           | what you want from them: a test that pass is essentially
           | useless, it completes the green check mark and that's it, it
           | doesn't matter. It's the test that fails that provide you
           | information, that matters to you.
           | 
           | One might be tempted to disagree, tell me that passing test
           | also provide information, by ensuring this and that and I
           | will insist that they take a second and realize that no, they
           | don't. Ultimately, the only value they have is in fact that
           | they can fail, it's always the failure that matter.
           | 
           | There are indeed tests which fails to fail for other
           | unplanned reasons, by that's not really what I'm getting
           | into, I'm aiming specifically for the attitude of tests for
           | the sake of tests.
        
         | jasonlotito wrote:
         | > I miss adult discourse.
         | 
         | This was always adult discourse.
         | 
         | We just have rose colored glasses to think otherwise.
        
         | bombela wrote:
         | But we are all a bit guilty of this I am sure. It is great to
         | share such stories. Realize we are all falling into the same
         | traps and too shameful to admit it. Then we can fix and improve
         | the technology.
        
           | nolok wrote:
           | Oh yes like I said in my other comment I totally agree and
           | have my fair share of such thing. And better reporting is
           | always better and a time saver.
           | 
           | But it's also important to remember why you write test: for
           | their failure cases, not their success.
        
         | TheDong wrote:
         | > Sorry to say but that too is your fault. First thing you're
         | supposed to do with tests is make them fail, then make them
         | pass
         | 
         | They did not say they didn't do that, you're assuming.
         | 
         | If I were the person here, I might write a simple test with
         | 'val1="foo"; val2="bar";' to make it fail. That would make it
         | fail because the first byte is different, while I would think
         | it failed because the entire value is different.
         | 
         | You might say that I should have also tried "bar" and "baz"
         | (which would reveal the issue), but it's infeasible to try all
         | possible inputs.
         | 
         | Most code is not written with exactly an excess of time to
         | follow perfect practices every time, and so we must make
         | tradeoffs.
        
       | ac130kz wrote:
       | Obviously, such an important tool like clangd should be run by
       | everyone, but saying that Clang is better is a false claim, Clang
       | is still slower than GCC.
        
       | VHRanger wrote:
       | If you're writing a new project, it's a good idea to try to have
       | it compile on all major compilers (and platforms, if possible)
       | from the get-go.
       | 
       | You catch a lot of these errors and UB. It will also force you to
       | code more idiomatically as you grow the project.
        
       | saghm wrote:
       | > Yes, this is a problem of weak static typing of pure C. Pure C
       | is a great tool for some jobs, but here be dragons, as they say.
       | 
       | > The problem is that (sizeof(struct ...)!=0)==1, so size=1
       | always for memcmp(). Instead of comparing two tmp, my code
       | compared only 1 byte of each structs.
       | 
       | This isn't just due to the type coercion of the third argument to
       | memcmp though, is it? It's at least as much due to the fact that
       | the _return value_ of memcmp is a valid `if` condition. I know
       | that "zero is false, everything else is true" for integers is
       | just as much a "feature" of C as the coercion of the comparison
       | to a size_t, but I feel like it's less obviously a useful
       | feature, given how easily it is to just manually write `!= 0` if
       | you happen to want the existing behavior. Are there any compiler
       | warnings or hints for being able to disallow this specifically?
        
         | djur wrote:
         | Most of the linters I'm aware of do the opposite. You could
         | probably create rules for one that require the condition of an
         | if statement to always contain a comparison, but it's worth
         | remembering that the comparison operators themselves return 0
         | and 1.
        
       | aew4ytasghe5 wrote:
       | OP's story is why I stopped using C++ about a decade ago.
        
         | jeffbee wrote:
         | The OP isn't using C++. In C++ you would just say
         | if (*m_result_original != *m_result_my_version)
         | 
         | No need for a call to a dumb old C function like memcmp.
        
       | Stratoscope wrote:
       | I mentioned this as an afterthought in a nested comment, but it
       | bears repeating.
       | 
       | The real problem here is not the bug in the memcmp() call, it's
       | that you should not use a memory compare to compare two structs
       | in a test _at all_.
       | 
       | If the structs have any padding, you are invoking undefined
       | behavior if the padding is not initialized.
       | 
       | And as we've seen, memcmp() opens the door to all kinds of
       | errors.
       | 
       | Don't rely on a compiler to catch this for you. Instead write the
       | code correctly in the first place.
       | 
       | The author's struct has x, y, and z fields, and the right way to
       | compare these structs is to explicitly compare these three
       | fields.
       | 
       | For example, based on the author's code (using a typedef for the
       | struct, and naming it xyz instead of tmp):
       | typedef int bool;            typedef struct {         int x, y,
       | z;       } xyz;            bool xyz_equal( xyz one, xyz two ) {
       | return(           one.x == two.x  &&           one.y == two.y  &&
       | one.z == two.z         );       }
       | 
       | Now you can use xyz_equal() in your test code and it won't have
       | any of the problems that the misuse of memcmp() caused.
        
         | Stracke wrote:
        
       | martin1975 wrote:
       | Indeed, clang has better error messaging than g++/gcc. Just my 2
       | cents, if you have a choice, do your C or C++ bit in Rust (which
       | like Clang, is llvm based). I've been writing C/C++ for a very
       | long time now, it's time to move away from it slowly....to a
       | better tool like Rust.
        
       | psyclobe wrote:
       | gcc is further along with c++20 support, which makes clang
       | nonstarter these days.
        
         | gavinray wrote:
         | I use Clang with GCC's C++ stdlib. You can do this with a
         | single flag:                 clang++ -std=c++2b --gcc-
         | toolchain=/usr/local/gcc-13.0.0-dev
         | 
         | Now I get to use LLVM with the latest C++23 support from GCC.
         | Have your cake and eat it, too =)
        
           | jeffbee wrote:
           | Even clang 15 cannot build the C++20 ranges/views support
           | from GNU libstdc++, no matter how many flags you give it.
        
       | owl57 wrote:
       | I'm slightly horrified with the specificity of this warning
       | (-Wmemsize-comparison). Apparently enough programmers make this
       | exact mistake.
        
       | gavinray wrote:
       | While we're talking about Clang errors, here's a neat trick I
       | discovered a few days ago:
       | 
       | LLVM comes bundled with a tool that will show you all the
       | errors/warnings in a nested, hierarchical fashion
       | 
       | Along with being color-coded by whether they're enabled by
       | default, and if they only exist for GCC compatibility.
       | 
       | You can display this by typing:                 $ diagtool tree
       | [optional-warning-level-like-Wall]
       | 
       | See: https://twitter.com/GavinRayDev/status/1593399093820129280
       | 
       | Here's a GIST where I collected some of the more obscure options
       | you may be interested in adding:
       | 
       | https://gist.github.com/GavinRay97/bae1d93925e55ce0a0084946f...
        
       | takeda wrote:
       | LOL. One month.
       | 
       | Isn't that case that unit tests were a waste of time? :P
        
         | CGamesPlay wrote:
         | He spent a full day* writing unit tests to cover half of the
         | important cases, then a month debugging why the code still
         | doesn't work, but you think the initial day was the waste of
         | time?
         | 
         | * He doesn't say how much time he actually spent, maybe he
         | spent a whole week writing his (poor) unit tests. Still, that
         | part was the waste of time?
        
       | not2b wrote:
       | The competition between gcc and clang has made both compilers
       | better. At this point, it is easy to construct examples that will
       | make either of the two compilers look a lot better than the
       | other. I think it is best for users if they continue to compete
       | and there continues to be no clear winner.
        
       | WalterBright wrote:
       | That is indeed a nice error message from clang. I presume it is
       | noticing that a `bool` is being implicitly converted to a
       | `size_t`, which is suspicious.
       | 
       | But still, it's good having both clang and GCC. The competition
       | makes both better. If there was only one, it would stagnate.
        
         | bombcar wrote:
         | And GCC targets fifty billion different instruction sets.
        
           | 77pt77 wrote:
           | doesn't clang also support many backends?
        
             | compiler-guy wrote:
             | Many, but not as many as gcc. Gcc being the only game in
             | town for about thirty years gave it a massive head start,
             | and gcc existed during an explosion in different isas and
             | architectures. Every obscure processor in the world seems
             | to have a gcc backend.
             | 
             | Clang much less so. But clang supports all the modern
             | "important" isas, so as a practical matter, it doesn't
             | matter that much for everyday use.
        
               | skissane wrote:
               | My impression is, GCC development team is more open to
               | mainlining support for obscure architectures, Clang/LLVM
               | require stronger evidence of demand and maintenance
               | commitment.
               | 
               | Of course, 99.9% of people don't use any obscure
               | architecture, so that's irrelevant to them.
        
       | stevefan1999 wrote:
       | Sure Clang compared to GCC, Clang would always be better coming
       | out at the top. But it's not code quality and better checking:
       | it's the spirit for openness.
        
       | suprjami wrote:
       | No one linter or static analysis tool will pick up every possible
       | error.
       | 
       | cppcheck picks this up (a bit cryptically):
       | clang-gcc.c:17:72: error: Invalid memcmp() argument nr 3. A non-
       | boolean value is required. [invalidFunctionArgBool]           if
       | (memcmp(m_result_original, m_result_my_version, sizeof(struct
       | tmp)!=0))
       | 
       | PVS-Studio picks it up too (more precisely):
       | <source>:17:1: error: V526 The 'memcmp' function returns 0 if
       | corresponding buffers are equal. Consider examining the condition
       | for mistakes.
       | 
       | flawfinder doesn't catch anything either. That doesn't mean
       | "cppcheck is better than flawfinder".
       | 
       | The error here is in the programmer relying on one tool as source
       | of truth.
        
       | nerdponx wrote:
       | > Or maybe I should stop using pure C...
       | 
       | Or at least stop jamming so much stuff into one line, and use
       | whitespace around operators!
       | 
       | To be fair to C, problems like this exist in a lot of languages,
       | even more-strongly-typed ones. At some point, a linter comes in
       | handy. It's nice if the compiler has linting built in, but at
       | some point you need to draw a line between "compiler
       | warnings/notes" and "lint to be found by an external tool".
        
       | senknvd wrote:
       | If you're using a decent build system[1], it's trivial to test
       | your code with Clang, GCC, and more:                   export
       | CC_LD=lld         CC=clang meson setup -Dbuildtype=debug
       | -Dwarning_level=3 build-debug-clang         CC=gcc meson setup
       | -Dbuildtype=debug -Dwarning_level=3 build-debug-gcc
       | CC=clang meson setup -Dbuildtype=debugoptimized -Dwarning_level=3
       | -Db_lundef=false -Db_sanitize=address build-asan
       | 
       | Now you can just                   ninja -C <name of build
       | folder>
       | 
       | to build any and all of these without affecting your source
       | directory. I usually wrap all the useful configurations in a
       | Makefile-like shell script.
       | 
       | [1]: https://mesonbuild.com/
        
         | jeffbee wrote:
         | Wow that's not trivial by any definition of the word. With
         | bazel and the standard bazel toolchain config, you just do:
         | CC=<whatever> bazel build
        
           | senknvd wrote:
           | You can do the same with Meson. I just added the warning,
           | debugging, and sanitizer options in-line for clarity. The
           | benefit (I'm not sure if Bazel has this too) is you only have
           | to run the commands once to set up the build directories.
           | After that, you can just use the ultra-fast ninja and almost
           | ignore Meson completely.
        
       | userbinator wrote:
       | When I see "!=0" in C code, it's immediately suspicious, along
       | the same lines as "== true" or "== false" (or worse, "!= true"
       | and "!= false", both of which I've seen in codebases before.)
       | Just write if(memcmp( ... )) or if(!memcmp(...)).
        
         | twiceaday wrote:
         | I disagree. I avoid implicit casting, including truthiness. "==
         | true" is indeed suspicious because you aren't avoiding
         | anything, but to me "!= 0" or "!= nullptr" are crisper.
        
           | djur wrote:
           | 0 is the false value in C, no implicit casting or
           | "truthiness" involved. The C standard explicitly defines if
           | statements in terms of zero: "the first substatement is
           | executed if the expression compares unequal to 0... [else] is
           | executed if the expression compares equal to 0".
           | 
           | C does not have a boolean type. (The `true` and `false`
           | defined in stdbool.h are macros expanding to the integer
           | literals 1 and 0, respectively.)
        
           | userbinator wrote:
           | I don't know what you mean by "crisper", but in general I've
           | noticed that extraneous code only increases the chance of
           | bugs (and this is a good example of that.) if(foo) and
           | if(!foo) are very common idioms and should be committed to
           | the subconscious memory.
        
           | WirelessGigabit wrote:
           | != 0 should only be used for ints / floats / 'numbers'.
           | 
           | Comparing it to false requires me to know that false is 0.
           | Comparing it to a ptr requires me to know that 0 represents a
           | pointer pointing to nothing.
        
         | 77pt77 wrote:
         | It's not suspicious.
         | 
         | Also, put the constant value (0 or true or null) on the left
         | side.
        
           | sigjuice wrote:
           | The Yoda conditional.
        
             | 77pt77 wrote:
             | TIL. Loved it!
        
             | [deleted]
        
         | dataflow wrote:
         | I would agree on this for _some_ cases (and I suspect more so
         | for != NULL than != 0), but memcmp() != 0 isn 't one of them.
         | There are multiple reasons why, some more generic than others,
         | but two of them are very specific to memcmp():
         | 
         | 1. There are similar functions (like
         | std::lexicographical_compare() in C++) returning bool instead
         | an int.
         | 
         | 2. It's not crazy to read memcmp() as comparing for equality
         | rather than ordering. i.e. I think when you see 'if (memcmp(a,
         | b, n))', it's quite natural for one's brain to read it as "if a
         | and b are equal..."
         | 
         | For someone who uses memcmp() on a daily basis, you probably
         | don't encounter these misconceptions. But as someone who writes
         | C++ and merely deals with C once in a while in the process,
         | both of these possibilities have tripped me up multiple times
         | when seeing if (memcmp(...)). Writing != 0 has always cleared
         | that confusion for me, since it immediately signals the result
         | (a) isn't meant to be read as a Boolean, and (b) might be
         | negative, thus putting my brain on alert.
        
           | Stratoscope wrote:
           | When I used to write a lot of C code, my solution for this
           | was to avoid calling memcmp() directly at all when I only
           | cared about a yes/no on the match. Instead I used a wrapper I
           | called memeq():                 int memeq( const void* lhs,
           | const void* rhs, size_t count ) {         return memcmp( lhs,
           | rhs, count ) == 0;       }
           | 
           | One could have an analogous memneq() with != 0, but I
           | preferred to just use the ! operator on the memeq() result.
           | 
           | I would also use a typedef for the struct, so you don't have
           | to keep writing "struct" everywhere:                 typedef
           | struct {         int x, y, z;       } tmp;
           | 
           | With those two tricks and more generous whitespace for
           | clarity, the erroneous memcmp() call becomes:
           | if( ! memeq( m_result_original, m_result_my_version,
           | sizeof(tmp) ) )
           | 
           | You're a lot less likely to get that wrong.
           | 
           | That said, this code, even with my updates, was wrong all
           | along. Using memcmp() on two structs, even with a wrapper, is
           | a bad idea. What if there is padding?!
           | 
           | This particular struct will not have padding, but in the more
           | general case you shouldn't count on that.
           | 
           | What you really need to do is have a function that explicitly
           | compares the x, y, and z fields of these two structs.
           | 
           | Then you won't have to worry about uninitialized padding, you
           | will never get the length wrong, and everything will always
           | just work.
        
         | kevin_thibedeau wrote:
         | There are coding standards that don't allow implicit truthiness
         | testing. They're misguided but they exist.
        
           | djur wrote:
           | In C? C doesn't have a concept of truthiness, just zero and
           | nonzero.
        
         | asveikau wrote:
         | This is a stylistic choice. In my personal opinion, I agree
         | with you. But I've seen styles that swear by the other way that
         | you and I don't like. And I respect those who hold those
         | opinions in good faith. I think someone once told me misra-c
         | requires != 0.
        
         | nrclark wrote:
         | I write comparisons against 0 or false/true sometimes. There
         | are places where it improves code clarity IMO.
        
       | scaredginger wrote:
       | The real issue here is that memcmp has a horrible interface that
       | only C programmers would find acceptable. The author seems to
       | understand this to some extent.
       | 
       | > Or maybe I should stop using pure C...
       | 
       | Yes, you should.
        
       | knorker wrote:
       | I know a company that used to build all code with both GCC and
       | clang in parallel on every build, and use binary output from GCC
       | but error/warning messages from clang.
        
         | jeffbee wrote:
         | That isn't quite what we did at Google but for many years we
         | did use clang for humans and GCC for releases. It's a good
         | compromise, IMHO.
        
         | pizlonator wrote:
         | I wonder if GCC still has a codegen edge over clang.
         | 
         | I know it used to. No idea if it's still the case.
        
           | aew4ytasghe5 wrote:
           | For x86 in certain cases, I think gcc produces better code
           | still. But x86_64 / aarch64 looks different, and there I
           | would assume the gap between gcc/clang is not big.
           | 
           | Would be cool to hear more about this.
        
         | yjftsjthsd-h wrote:
         | Honestly if nothing else that sounds amazing from the
         | perspective of maintaining portability. Allowing you to exploit
         | the strengths of each compiler is also nice:)
        
         | vbezhenar wrote:
         | It is wise because this dichotomy won't last forever. GCC will
         | slowly die (because corporations seems to hate GPL and LLVM is
         | their child) and everyone will need to migrate to LLVM sooner
         | or later.
        
           | ndiddy wrote:
           | Ironically, while LLVM lives on, Clang is slowly dying and is
           | failing to compete with GCC. Clang's C++20 support lags
           | behind GCC because both of its major corporate sponsors,
           | Google and Apple, have seemingly moved on (after failing to
           | get the ABI changed, Google went over to working on Carbon,
           | and Apple doesn't care as long as the C++ support is good
           | enough to compile LLVM and the C++14 subset required for
           | Metal's shading language). All the other vendors who take
           | advantage of Clang's permissive licensing seem unwilling to
           | upstream improved C++ support.
        
       ___________________________________________________________________
       (page generated 2022-11-28 05:01 UTC)