[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)