
So you think you know C? (2016) - pcr910303
https://wordsandbuttons.online/so_you_think_you_know_c.html
======
WalterBright
Having written a conforming C compiler, at one point I knew everything there
was to know about C (I forget details now and then, or confusing them with C++
and D).

But knowing every engineering detail is not the same thing as knowing how to
program in C effectively. It's like being the engineer who designs a Grand
Prix car. It does not mean you can drive it faster around the track than
anyone else. Not even close.

For example, the C preprocessor is surprisingly complicated. I had to scrap it
and rewrite it completely 3 times. If you try to make use of all those
oddities, my advice is don't waste your time. Over time I removed all the C
preprocessor tricks from my own code and just wrote ordinary C in its place.
Much better.

~~~
pcwalton
I couldn't agree more. After spending many years working with LLVM, which is
at its heart a C compiler, and understanding why it has to do the sometimes-
terrifying things it has to do to get C to run well, I've become very paranoid
when writing C or C++. My C/C++ code is as boring as possible.

(In fact I try to avoid writing C or C++ whenever possible these days;
undefined behavior in the language is too pernicious and unfixable without
breaking compatibility. I think both languages are approaching obsolescence.)

~~~
dvdbloc
What would we write drivers in then if C was obsoleted?

~~~
gpderetta
pcwalton might have an opinion :). He is Rust lead designer.

~~~
pcwalton
Not lead designer :)

But yes, Rust, or even in userspace, as newer and/or more microkernel-ish OS's
allow for. Apple is doing work to allow drivers to be written in Swift...

~~~
saagarjha
I believe DriverKit is still C++.

~~~
pjmlp
Just some parts of it.

Some device classes (not to mix with OOP ones) can only be programmed with
C++, while others can be developed in any compiled language able to link to
the OS APIs.

There is a WWDC session on it.

~~~
saagarjha
I watched it, and they're pretty clear that driver extensions (using
DriverKit, like I mentioned) must be written in C or C++. System extensions
can use any language.

~~~
pjmlp
I was thinking about whole driver feature set, so got it wrong.

------
pksadiq
There are a few problems with the questionnaire. "I don't know" is pretty
generic choice to be given/choosen.

Say for example, in question 5, the statement "return i++ + ++i;" is
undefined, because the value of i is read and modified twice in a single
sequence point (and of course, the order of addition is unspecified), which is
not allowed in C. So the answer is "Undefined." (The explanation given in the
page not accurate enough in terms of C)

And for question 1, the code is valid, but the result is not strictly defined.
It depends on the implementation. So the answer is "Implementation defined."

The usage of "main()" hurts me, the strictly conforming way is to write it as
"int main(void)" (or similar)

I feel like the questionnaire piss off people who really knows C.

~~~
Dylan16807
> So the answer is "Undefined."

The code is undefined, but "I don't know." is still the correct answer for
what happens to the variable.

~~~
pksadiq
> The code is undefined, but "I don't know." is still the correct answer for
> what happens to the variable.

Well, then a better choice would be "I can't know."

~~~
Filligree
Compile it, look at the assembly. You can know. The answer will vary from
place to place, but it isn't non-existent.

~~~
thedufer
With what compiler? Targeting what architecture? The question is
underspecified. "I can't know" is correct.

~~~
Gibbon1
Q: What is a leaky partial abstraction of the C standard?

Ans: A compiler.

------
dmcdm
I found it an amusing excrcise, if not terribly relevant, even as someone who
spends 90% of his dev time in C.

What rubs me about these sorts of articles is they make some presumption about
the importance and nessecisity of writing truely portable C, as if the "C
Standard" were in and of itself a terribly useful tool. This is in contrast to
where I live most of the time which is "GCC as an assembler macro language"
(for a popular exposition on this subject see
[https://raphlinus.github.io/programming/rust/2018/08/17/unde...](https://raphlinus.github.io/programming/rust/2018/08/17/undefined-
behavior.html)). And yeah, reading through the problem set I was critiquing it
in context of my shop's standards, where we might be packing and padding,
using cacheline alignment, static assertions about sizeof things, specific
integer types, etc. So these sorts of articles just come off as a little
pendantic to folks like me. I don't doubt they're useful for some folks, and I
guess it's interesting to come up from the depths of non-standard GNU
extensions and march= flags to see what I take for granted.

~~~
userbinator
It's very much worth reading, Linus Torvalds' opinion of standards that's
linked in that article, but I'll link it again here:
[https://lkml.org/lkml/2018/6/5/769](https://lkml.org/lkml/2018/6/5/769)

"So standards are not some kind of holy book that has to be revered. Standards
too need to be questioned."

The way I see it, a lot of compiler writers are basically taking the standard
as gospel and ignoring everything else "because the standard doesn't say we
can't" \--- and that's a huge problem, because behaviour that the standard
doesn't define often has a far more common-sense meaning that programmers
expect. IMHO the onus should really be on the authors of compilers to find
that reasonable meaning. In fact, the standard even suggests that one possible
undefined behaviour is something like "behave in a manner characteristic of
the environment" (can't remember nor be bothered looking up the standard.)

~~~
pcwalton
This is a common misconception. Compiler authors don't exploit undefined
behavior to make themselves seem smart, or because they like breaking code.
They exploit undefined behavior because somebody filed a bug saying some code
was slow, and exploiting UB was the simplest way--or, in many cases, the
_only_ way--to fix the performance problem.

GCC and Clang _do_ give you the option to avoid optimizations based on
undefined behavior: compile at -O0. We think of the low-level nature of C as
being good for optimization, but in many cases the C language as people expect
it to work is at odds with fast code.

It's fascinating to actually dive into the specific instances of undefined
behavior exploitation that get the most complaints. In each such case, there
is virtually always a good reason for it. For example, treating signed
overflow of integers as UB is important to avoid polluting perfectly ordinary
loops with movsx instructions everywhere on x86-64. It's easy to see why
compiler developers added these optimizations: someone filed a bug saying
"hey, why is my loop full of movsx", and the developers fixed the problem.

Edit: Should be movsx instead of movzx, sorry.

~~~
jstimpfle
Could you go into a little bit more detail regarding the movzx? Aren't 32-bit
registers always zero-extended on x86-64?

~~~
pcwalton
Sure. Here's an in-depth explanation from Fabian Giesen:
[https://gist.github.com/rygorous/e0f055bfb74e3d5f0af20690759...](https://gist.github.com/rygorous/e0f055bfb74e3d5f0af20690759de5a7)

~~~
jstimpfle
Thanks, rygorous is always a great read - although sometimes a little
overwhelming. If I got the gist of it, I have a small correction to your
comment: the issue is about movsxd (sign extended integer indexes), not movzx
(zero extension).

------
bcaa7f3a8bbc
My score is 3/5.

One immediate redflag I have noticed is using "int", "char", "short" as if
they have a definite size. They don't. C standard only guarantees a minimum
size. For example, many PDPs are 36-bit. Assuming the size of a variable is a
common practice nowadays, but at least one should use uint8_t, int32_t, etc.
from stdint.h.

But I was still tricked, it should be obvious in hindsight, 12 years of
schooling led me to think: If the author was asking these questions, at least
one or two questions must be answerable (even if it's technically incorrect,
but you'd better to guess the _original intention_ of a question). So I still
tried to guess and got two wrong answers... Get to be careful next time...

~~~
Raymonf
In school, I was taught to choose the "best answer" on a test or quiz, and if
you don't know something, choose the answer that looks right to you.

This test.. it reverses that entirely.

~~~
NohatCoder
It is school that gets it wrong. In real life, knowing that you don't know
something and acting accordingly is often way better than taking a guess.

------
beached_whale
The third one has another implementation defined aspect. We do not know the
value of a space ' ', in ascii it is 0x20(32) but that depends on the system,
in EBCDIC it is 0x40(64).

------
Ultimatt
Replacing "I don't know" with "this is undefined in the spec" or "this is
implementation specific" would probably increase the pass rates.

------
flowerlad
“I don’t know” is not the right answer. I do know. I know the answer to be
“Unspecified”.

~~~
thayne
But you _don't_ know what it returns, because the behavior is either undefined
or implementation defined, and you don't know the implementation.

~~~
ben509
But you _do_ know that it's undefined or implementation defined. It's a "known
unknown".

~~~
H8crilA
Look, do you know what will it return or not?

~~~
saagarjha
Not without knowing the compiler, which I think was the point.

------
vkaku
I answered Idk to all. After the first two, the pattern became clear and I
felt like if I wrote a compiler myself, the answers could be very different.

I've worked on 16 bit C code, 32 and now 64 bit code. So I knew that the
behavior was implementation and optimization dependent. :)

Ignorance is bliss in C.

------
dang
Discussed at the time:
[https://news.ycombinator.com/item?id=12902304](https://news.ycombinator.com/item?id=12902304)

[https://news.ycombinator.com/item?id=12900279](https://news.ycombinator.com/item?id=12900279)

Similarly titled in 2012:
[https://news.ycombinator.com/item?id=4657317](https://news.ycombinator.com/item?id=4657317)

and 2011:
[https://news.ycombinator.com/item?id=3125891](https://news.ycombinator.com/item?id=3125891)

------
childintime
I posted this in the embedded C-shop I work, under the comment that exactly in
this place all should pass this test. Sadly only 1 in 5 passed the test (yours
truly). Admittedly, this test was binary: either you pass or fail all of the
questions (which is also sort of a give-away).

In the end this test proved to be a really valuable, because the "I don't
know" drove the point home, specially for smart folks who don't like to answer
_any_ test, ever, with "I don't know".

------
Sir_Cmpwn
5/5 - but I came to Hacker News to procrastinate from the C parser I've been
writing.

~~~
NieDzejkob
Wait, which of your projects needs a C parser?!

~~~
Sir_Cmpwn
A new one

------
cat_plus_plus
Well, that's a copout. Of course, if you take absolutely any computer
architecture, you can't assume simple things like sizeof(int) or data
structure alignment. But if sizeof(int) is at 4 and data needs to be aligned
by its own size - like on any real architecture relevant today - many of these
questions have a deterministic answer. In practice, compiler bugs are a much
bigger issue than architecture assumptions.

~~~
tillulen
What’s the latest compiler bug that caused a serious issue for you? (Genuinely
interested, no sarcasm here.)

------
usr1106
I failed on one, number 4. I bravely assumed 16 bit integers cannot exist. Can
anyone name a concrete platform/compiler where int is/was 16 bit. Or is this
just a theoretical option left open by the spec?

~~~
inamberclad
AVR, so certain Arduinos. On an Arduino Uno, sizeof(int) returns 2.

~~~
segfaultbuserr
So does AVR's (ex?) competitor, PIC8. This comes from the documentation of
Microchip's C18 compiler.
[https://i.stack.imgur.com/1uV3l.jpg](https://i.stack.imgur.com/1uV3l.jpg)

------
wrs
Sad to say I scored perfectly, due to a similar early disillusionment on
embedded platforms, and years of pain porting code between 16- and 32-bit
architectures when the author thought they knew the size of “int”.

------
danbolt
At the end of the test, the author talks about automation programming for a
nuclear power plant. I don’t think I could ever sleep the same at night after
writing something like that.

~~~
OldHand2018
> I don’t think I could ever sleep the same at night after writing something
> like that

In these situations, you likely know your hardware and know your compiler, so
you can actually provide an answer for 4 of the questions. The last one is a
situation where someone should tell you not to get cute in the code review.

I wrote C in telecom and finance and in both places we enforced a rule: when
you define a structure, put a comment after each element that says what you
_think_ the structure offset should be, and at the end of the structure
#define a constant that says what you think the size of the structure should
be. In a code review, if anyone noticed something that didn't look right, you
could talk about it. In testing, you could also check that sizeof(foo_s) ==
FOO_S_SIZE and fail if it wasn't.

In some of our code, we would test the size of various types and structures on
startup and immediately exit if they weren't what we expected. We'd print type
sizes to logs to help debugging if there was ever a problem. We were
supporting a single code base that ran on big endian, little endian, X86,
Itanium, SPARC, ARM. Compilers change, but automated tests of type and
structure sizes catch things immediately.

It may sound like a lot of work, but it actually isn't at all. It also helps a
lot with long-term maintainability.

~~~
bzbarsky
> In some of our code, we would test the size of various types and structures
> on startup

This is one of the things that C++ has actually improved a lot recently: doing
this with static_assert is much nicer in terms of catching problems early...
And yes, it's great for long-term maintainability.

~~~
unwind
C has had standardized static assert since the C11 spec was released. See [1]
for instance.

[1]:
[https://stackoverflow.com/a/7287341](https://stackoverflow.com/a/7287341)

~~~
saagarjha
I never thought I’d hear myself saying this, but that syntax is uglier than
C++’s.

------
icedchai
I scored perfectly. I've been programming in C since 1989, on various
platforms (started with the Amiga, then VAX/VMS, Linux x86, and various
embedded systems.)

------
sys_64738
I emailed this to my boss telling him I got 0/5\. He just setup a 1:1 meeting
first thing Monday AM.

~~~
Something1234
Let us know how it goes...

------
fantasticFerret
C programer here. I try to never think assume I know C.

------
morpheuskafka
If we're going to be --pedantic, shouldn't the author specify the exact
standard of the C language under test? A lot of companies have varying
implementations of C and perhaps some do specify some of the behavior at hand
here.

~~~
saagarjha
Implementations of C≠C standards, though of course I'm sure some random
obscure compiler has tried to call their C dialect a "standard" at some point.

------
zw123456
The better wording would be D) not enough information to give a definitive
answer. It's like the old gotchya question; what is 1+1 ? of course the answer
is it depends on if you are using binary or a base of integer >2.

~~~
rgoulter
Yeah, or "undefined behaviour".

But that reminds me of the joke: there are 10 kinds of people: those who know
binary, those who don't, and those who didn't know the '10' was written in
base 3.

------
bandushrew
4/5 I tricked myself into thinking I could figure out the last one....oops.

------
chadcmulligan
Isn't there a more fundamental flaw in these questions? main() always returns
an int, whether that's 4 or 8 bytes, 0 or 1 means success or failure depends
on the implementation. here's a bit of a discussion
[https://stackoverflow.com/questions/204476/what-should-
main-...](https://stackoverflow.com/questions/204476/what-should-main-return-
in-c-and-c)

Reminds me of the dumb exams some teachers would set to trick you when in
school to make themselves feel superior.

~~~
TazeTSchnitzel
What's wrong with it returning int here? They aren't using any variables
bigger int, and the meaning of return codes is irrelevant.

------
dahart
> And at this point, I only have to apologize. The test is clearly provocative
> and may even be a little offensive. I’m sorry if it causes any aggravation.
> [...] It was a research project in nuclear power plant automation, where
> absolutely no underspecification was tolerable.

I appreciate the apology here, and I can totally understand the concern about
the spec in a safety critical environment.

Still, all questions on this test except the first are clearly examples of
things you should never ever do in production code, which might undermine the
message a bit? Yes, you can write bad code, and that’s true in every language
I’ve ever used.

I’m guessing it would be hard to find a modern compiler on Windows, Mac or
Linux that produced padding other than rounding up to nearest 4 bytes?

Sizeof(a+b) is obviously a weird thing to do.

char a = ‘ ‘ * 13 produces an overflow warning in gcc.

(((((i >= i) << i) >> i) <= i)) I hope nobody really did that.

return i++ + ++i; Not doing exactly this was drilled into us in CS 101. Still,
I’d be interested to hear about a compiler that doesn’t return 1, since many
people rely on the fact that ++i is pre-increment and i++ is post-increment. I
don’t doubt one is out there, I’m curious to know which.

There probably weren’t much better choices 20 years ago... what would be the
best choices today for a branch new nuclear power plant?

~~~
noahmorrison
_Still, I’d be interested to hear about a compiler that doesn’t return 1_

gcc (Ubuntu 8.3.0-6ubuntu1~18.10.1) 8.3.0 returns 2 (executes from left to
right... at least the first time I ran it).

~~~
dahart
Wow, you’re right. Me too on ubu 16. Okay, I guess it’s not about pre or post
increment. Maybe I should read the spec... ;) And good reason not to do this
in code!

------
m_kim
But a code based on several standards, not only on C standard. For example we
know, that basic source character sets should contain space (C11 5.2.1), we
know that character constants has type "int" and that character constants
represent value equal to the code of a symbol (C11 5.4.4). We know used source
character set and we know code of the space character. We can configure
specific source character set on the POSIX compatible systems. We know that
return statement in the main function equivalent to the call to exit function
(C11 5.1.2.2.3), we know that only 8 least significant bits will be used from
returned value (POSIX.1-2001 definition of "exit"), we know that INT_MAX
should be at least 32767 (C11 5.2.4.2.1) so we sure that the result that we
got from "return" statement in the "main" function is positive integer from 0
to 512. Finally if we configure source character set to be sure that ' ' has
code 32 we know for sure that we got value 416 in the specified example. So we
know for sure answer on question 3 based on the C11, POSIX.1-2017, and ISO 646
standards.

~~~
NieDzejkob
I don't know if POSIX specifies CHAR_MAX, but on most systems, storing the
value into a char will change the value, making it everything but 416:
[https://tio.run/##S0oszvifnFiiYFeSWlyil6xgY@Pq7/Y/M69EITcxM0...](https://tio.run/##S0oszvifnFiiYFeSWlyil6xgY@Pq7/Y/M69EITcxM0@jLD8zRVOhmktBITkjsUghUcFWQR0ItRQMja2BgkWpJaVFeQqJ1ly1/4H6uLjSk5MVICZx6ekn6uWXlnClJmfkK6jY/wcA)

~~~
m_kim
My mistake: We have (32*13) with minimal possible CHAR_BIT is 8. So it either
416 for char bigger then eight bits, or 160 for unsigned eight bits char, or
-96 for signed eight bits char. Then it extended to the signed integer value
(one of this three values), and then we got result as (int)(status & 0377).
For all three cases result will be 160.

------
rramadass
This quiz wasn't illuminating at all. You generally start with assuming and
validating a "C Datatype Model" i.e. ILP32/LP64 etc. for your system. Once you
know that, these questions are easily answered.

If you want to see some real tricky C code, see some of the articles here:
[https://locklessinc.com/articles/](https://locklessinc.com/articles/)

------
chubot
OK you tricked me. But the test would have been better if the answer was
"undefined behavior" instead of "I don't know".

~~~
badamp
Not all of them are undefined behavior

~~~
staticassertion
Sure, but "I don't know" makes it seem like you're just saying "pass", which,
when you finish the test, is obviously not what it represents.

------
strenholme
The one that got me is this:

uint32_t foo = 1; foo <<= 32;

Here, I figured that foo would always be 0. Wrong. It was always 0 with GCC,
but this is undefined in the spec and code like this can have a different
value in clang. I actually had to make a security update to my little open
source project because of this (although the code I wrote did not manifest the
bug in an insecure way, even with clang).

------
kabes
I don't know any c, so I answered "I don't know" on all the questions. Turns
out I'm actually a C genius.

------
zoomablemind
> Eventually, I had to learn to rely on the standard instead of folklore; to
> trust measurements and not presumptions...

Indeed, testing your assumptions, because even a defined standard may result
in a differing implementation of it. Especially in critical applications,
testing the expectations gives some sense of a defined behavior.

This quizz is amusing as a mental exercise and a parable, but in reality all
of these cases had to be fleshed out on a real platform, with real compiler
and ... specified expectations of the behavior.

None of the cases in fact communicate a clear intent, except maybe #1 to
figure out the padded size, still it's somewhat open-ended. Perhaps returning
a specific condition (return sizeof(struct ...)==5; ) would show a clear
intent. Not that it would change the right answer, just such a case may indeed
be true on a specific platform, compile flags erc.

~~~
zoomablemind
Just to flesh this out, I put the puzzle code through gcc 6.3, on ideone

[https://ideone.com/PziSzq](https://ideone.com/PziSzq)

Result:

1:8 2:0 3:-96 4:1 5:2

Sure, there's a bunch of warnings emitted... fortunately.

------
kahlonel
Pro tip: avoid `int`s as much as possible in your C code. Use <stdint.h>
defined macros instead.

~~~
saagarjha
There is a time and place for int32_t.

~~~
AdieuToLogic
> There is a time and place for int32_t.

And time_t[0] is not one.

;-)

0 -
[https://pubs.opengroup.org/onlinepubs/9699919799/functions/t...](https://pubs.opengroup.org/onlinepubs/9699919799/functions/time.html)

~~~
AdieuToLogic
I guess that joke was hard to C.

------
_kst_
I submitted a GitHub issue for the `main()` definitions and some other
quibbles.

[https://github.com/akalenuk/wordsandbuttons/issues/12](https://github.com/akalenuk/wordsandbuttons/issues/12)

~~~
_kst_
It's been corrected in the repo, but as I write this the web page has not been
updated.

------
ridiculous_fish
UB nasal demons etc etc.

But often we encounter UB in code that's already shipped. So it's good to have
an intuition about what machine code was actually emitted, for example when
deciding if a crash report is due to this particular UB, or not.

~~~
saagarjha
Or you could just look at the binary you shipped: by the time it’s compiled,
you don’t really have to deal with undefined behavior.

------
Tharre
5/5, but I don't think this test is very good at capturing the more obscure
features of C, they all just deal with the fact that platforms have different
datatypes/alignment requirements, except for the last one. I think a better
example would be the following:

    
    
      int a=1, b=2, i, j;
      i = a += 2, a + b;
      j = (a += 2, a + b);
    

Whats the value of a, b, i, j? Hint: i and j are different.

Which begs the question, why does C have those features in the first place?
The only C code where it's reasonably common seems to be in crypto algorithms.

~~~
umanwizard
5 and 7? If I remember the behavior of the comma operator correctly.

Anyway, I am inclined to agree that these are misfeatures. I’d almost
certainly ask for it to be changed in code review.

------
jtl999
I knew there was something up because long ago when developing for Arduino
boards as part of a course, my mentor educated me on the difference of size of
datatypes across different architectures.

------
squarefoot
I scored 2, but mostly out of luck for being 100% sure only about the 1st
question, as I encountered alignment problems a lot of times in the past when
dealing with structures to be sent through the network, and also between
different architectures (and sometimes endianess too). If memory serves, there
are #pragma directives to force the compiler to align structure members to a
given interval, but they're compiler dependent and would make a non portable
piece of code even less portable.

------
professorTuring
It is curious because, in each of the questions I encountered problems with
the own questionnaire and I tended to answer in the way the writer thought.

And then you find "it's a trap".

I think the questionnaire is not honest enought, a better answer for D should
have been: "we need more information" or "there are programming
inconsistences"...

I thought that when selecting "I don't know" I was telling "I don't know
what's happening with the code and the inner datails".

------
zzo38computer
I managed to answer all of the questions correctly; I recognized them as
undefined. Still, there may be enough definition on the set of computers the
program will run on, to do, in some cases (for example, that char is at least
8-bits, and/or that it is ASCII).

But the thing that would be better to do, in my opinion, may be like having
LLVM with macros (including standard macros for dealing with differences of
systems, and user-defined macros for your own use).

------
heinrichhartman
There is a difference between "I don't know" and "undefined behaviour".

I know that most of this stuff is undefined according to the spec. But I
(might) also know what my particular GCC version does in these cases.

What I know for sure, that those programs do output something, and it's not "I
don't know" (the string) ; )

What I don't know, is what level of sophistication the author assumes.

------
nanoanon
Funny. My first thought of question one is we'd need to make assumptions about
which architecture we're working on to know this answer. By the time I got to
question 3, I realized the author's trend. This is both the curse and blessing
of C, a language that gives you just barely a high level translation layer
over the raw silicon.

------
zw123456
It is not difficult to overcome these limitations of C by typedef definitive
types like signed int32, unsigned int8 and so on. Many embedded C .h have that
as a standard way of clearing things up. of course you can always sizeof(int)
or whatever. (BTW this quiz or one like has been around a long time, but still
a good reminder).

------
nappy-doo
The author's explanations of the first three answers aren't sufficient. There
is no requirement within C for `int` and `char` to be different sizes.
Similarly, you don't know what the resolution of ' '*13 will be. It's
architecture dependent.

C, for all it's simplicity, is a relatively complex language.

------
FraKtus
At least for the third question (char a = ' ' * 13;) XCode warns "Implicit
conversion from 'int' to 'char' changes value from 416 to -96".

And for the fifth (return i++ + ++i;) it warns "Multiple unsequenced
modifications to 'i'".

I would not skip those warnings.

------
caf
There is a minor error in the explanation for the third one: the minimum
allowed value for CHAR_BIT in C is 8 (it does not affect the result,
principally because the value of ' ' could be anything in the range of char).

~~~
jepler
That's the one where I wanted most to quibble with the "explanation" of why
there is no set answer: To me, the top of the line answer is that the value of
the string constant ' ' as an integer is implementation-defined (or maybe it
depends on the execution environment? See, I'll get the precise wording wong
too); anyway, space is 32 in ASCII but 64 in EBCDIC. The most you could say is
that it's not zero (and maybe that it's not -1? I'd have to check how EOF is
defined)..

------
e1ghtSpace
My friend doesn't know how to code and got the whole questionnaire correct.

------
redbumble
Can't even make the test. Which version of C? Which platform? Under DOS in the
90's, the answer to the first question would have been 3, it's not even
proposed in the options.

~~~
Sharlin
Each of the questions has exactly one answer that is correct regardless of
version and platform.

~~~
redbumble
It would be correct to answer that I don't have enough information to choose
an option. It's incorrect to answer that I don't know.

~~~
Sharlin
No, but there are (at least) two interpretations of ”I don’t know.” It’s a
nice little exercise in epistemology.

------
baalimago
This must be the first time my lack of knowledge gave me full score.

------
mhh__
This is the language we still choose to trust our credit cards with.

~~~
rramadass
I hope you were joking. This is just a "gotcha" quiz and not relevant at all.

~~~
mhh__
I'm almost not. I made the point because this level of ambiguity and "do it
yourself" is consistent throughout the language.

I know why we still use C, but the use of C is inherently prone to security
problems.

C does not provide bounds checking by default, so it can be forgotten
(Heartbleed) and the lack of either static checking, RAII or garbage
collection (Not as a library e.g. Boehm) makes memory corruption all but
inevitable.

~~~
rramadass
People are forgetting that it is the very "looseness" of C that is responsible
for its great success. The sheer volume of code in C (specifically, any number
of complex and critical software) is a testament to that. People keep
parroting the same old tired tropes about C without reflection and thought.
All the problems, both real and imaginary, in the language have been worked
through/around since the beginning by simple discipline, guidelines and
external libraries. I am always annoyed when people bring up "memory
corruption" as if it were some primordial sin. The power to manipulate raw
memory in whatever way i want is so crucial that i am willing to live with the
downside of possible corruption. In fact most of the people i have worked with
and myself never found this to be so much of a problem as everybody else makes
it out to be. We always followed good guidelines, had special libraries for
memory allocation as needed and testing procedures to catch memory leaks.
Everything worked out fine.

In conclusion, the power given to Programmers by C far outweighs any of its
perceived downsides in real-world scenarios.

~~~
mhh__
This is fine for your or my software but the risk of these bugs no matter how
rare is too great for mass deployed code in something similar to OpenSSL.

Any good alternative still allows you manipulate raw memory, but provide a
safe alternative which makes it much harder to fuck up.

What power do I actually lose by using a safer language?

~~~
rramadass
The OpenSSL "Heartbleed" bug that you bring-up is not related to inherent
failures of the C language but something else. Just as an aside, i actually
have some background in implementation of security protocols (specifically
IPSec framework) and FIPS certification for a cryptographic algorithms
library, though by no means am i an expert. In the security community many
people believe that "Heartbleed" was an intentional plant. See
[https://www.smh.com.au/technology/man-who-introduced-
serious...](https://www.smh.com.au/technology/man-who-introduced-serious-
heartbleed-security-flaw-denies-he-inserted-it-
deliberately-20140410-zqta1.html) OpenSSL is such a heavily used and vetted
piece of software that the probability of this being an "accidental bug" is
very very low and my money is on it having been deliberately inserted i.e.
deliberately used C language features towards a nefarious goal. So this is not
a good example to bring up.

Now coming to your other point, in today's environment, it is true that you do
not lose much for the most part when using a safer language because somebody
else has done the dirty work in the implementation of the corresponding
language's runtimes, compilers, libraries and ABIs. Without the latter you
cannot have the former. After all at some point you have to move out of the
cocoon provided by the language and meet real hardware (a good example is
bare-metal programming on MCUs). And that is where C is needed and any
challengers have to provide exactly similar "ugly, dangerous and unsafe"
features if they want to dethrone the champ.

------
seba_dos1
4/5, forgot that integer promotion is more complicated than what intuition
would tell you.

To be honest if was pretty obvious what the right answer was, but tried to
answer honestly anyway ;)

~~~
ummonk
Yeah I assumed short would be at least as big as a char and would this be
comparing size of short against short. Didn’t realize it would get promoted to
int.

------
GnarfGnarf
I took it as "What will this do on your compiler?"

------
kuroguro
Agh, 0/5! They were correct in the context of MSVC tho.

------
GorgeRonde
Went to an IRC chat room when I was learning C in school. Asked if you could
return a pointer to something that lives on the stack. Was talked down by an
all-knowing dude telling me to go read K&R again. Proceeded to write a code
sample [1] that showed it is possible (it's not really stable but works
reliably in recursive calls IIRC).

I do not like this attitude (then again it was just one random dude).

[1] [https://www.onlinegdb.com/HyO5VXRxS](https://www.onlinegdb.com/HyO5VXRxS)

~~~
dahart
> it is possible (it’s not really stable but works reliably in recursive calls
> IIRC). [...] I do not like this attitude

You might want to listen. You’re getting the K&R comment and the downvotes
because this does not work, ever. It’s a really, really bad idea. In recursive
calls, it might not crash right away, but you will have bad data, the memory
at the pointer address will have been overwritten by the next stack frame
that’s placed there.

Don’t ever return pointers to local memory because the memory is “gone” and
unsafe to use the moment your function returns. Even if you try it and think
it works, it can and probably will crash or run incorrectly in any other
scenario - different person, different computer, different compiler, different
day...

Your comments about getting a warning and ‘However if you wrap the local’s
address... it “works”’ should be clues. The warning is the compiler telling
you not to do it. The workaround doesn’t work, it only compiles. By using
aliasing, you’re only tricking the compiler into not warning you, but the
warning is there for a reason.

~~~
GorgeRonde
Listening to what ? To the dude that tells me that's not possible and proceeds
to dump a big pile of authority on top of my head or to my own experiment that
tells me another story ?

I would have preferred to be told:

\- yes and no. You'll get warnings if you try to return a pointer to a local,
however, doing this and that, you can manage to do it.

\- but once you have achieved that, the result will be dependent on the way
the stack is handled (not really in your control). You'll feel some comfort
doing this in recursive calls, however beware of signal.h.

But this isn't the answer I received. I guess C programmers do not know the
difference between what you can do (however risky) and what you shouldn't do.
Also when someone asks such "weird" questions, do not assume he's a beginner
with no notion of what constructs he can handle safely, maybe he's someone
trying to find the limits of C – and once these limits are identified it can
be a good conversation starter about C's internal and the way various
compilers differ.

Edit: also downvotes on HN are not like downvotes on Reddit: there's actually
a limit (-2 ?). Below this the comment disappears. Conclusion: only downvote
when the comment engages in antisocial behavior (not respecting the rules or
common human decency, etc ...), not when you disagree with it. I always upvote
an unfairly downvoted comment for these reasons.

~~~
dahart
I was trying to help by explaining it, instead of saying go read K&R, but I
don’t get the feeling you really heard or understood me. There is no other
story. There is no yes and no. There is only no. You cannot manage to do it.
It does not work to return local memory from a function, ever, period. Once
you return, it is 100% unsafe to try to use the memory from your previous
stack. There is absolute zero comfort in recursive calls.

You are mistaking some luck in having it not crash once for thinking that it’s
okay in some situations. It’s not okay under any circumstances. That’s what
makes this even more dangerous. Your program could crash at any time. It might
run a thousand times and then suddenly start crashing. It might always run for
you, and then crash on other people. But just because it runs once without
crashing doesn’t mean it’s working.

A signal is not the only way your function’s stack can get stomped on the very
next instruction after you return. Other processes and other threads can do
it, the memory system can relocate your program or another one into your
previous memory space. Recursive calls are guaranteed to stomp on previous
stack frames when your recursion depth decreases and then increases, the
previous stack will be overwritten.

Returning a pointer to a local stack frame is _always_ incorrect. It’s not
risky, it’s wrong.

BTW: you have the ability to see comments below the downvote limit, go to your
profile settings and turn on showdead.

I didn’t downvote you, if that’s why you were trying to explain voting
behavior to me, but you will find on HN that downvotes and upvotes both happen
for a wide variety of reasons, and are not limited to either whether people
agree, nor whether the comments are polite. Downvotes are often cast for
comments that break site guidelines, for example just failing to assume good
faith can get you downvoted. So can making blanket generalizations about a
group of people, like the above “I guess C programmers do not know the
difference...”. See the comments section here:
[https://news.ycombinator.com/newsguidelines.html](https://news.ycombinator.com/newsguidelines.html)

I sometimes upvote what appear to be unfairly downvoted comments to me. I
usually upvote people who read and respond to me, regardless of whether I
agree with them.

~~~
GorgeRonde

        - Java: no
        - Ruby: no
        - PHP:  no
        - C:    yes and no

~~~
dahart
?? I don’t understand what you mean. Those other languages don’t have
pointers, they only have references, but what do they have to do with this?

Why do you still think there’s some yes in C? It’s not making sense yet that
your memory is gone after you return? Returning a pointer to a local variable
is exactly the same as calling delete or free on a pointer and then reading
from it. You officially don’t own the memory after a return statement, so if
you try to use it, then what happens is indeterminate. Again, since it doesn’t
seem to be sinking in: it is _always_ wrong to return a pointer to local
memory. But, if you really really don’t want to listen, and you’re sure it
works sometimes, then I say go for it!

------
layoutIfNeeded
By the third question it was obvious that all of them are implementation-
dependent or undefined behaviour.

------
mpweiher
While I got my 5/5, I think the answers should have been “I seriously don’t
_want_ to know”.

------
emmelaich
Don't we need to know the sizeof int and void* on this machine?

[edit - that seems to be the point sigh]

------
ChuckMcM
tl;dr - C has some undefined behavior, if you plan on things working based on
your experience with one compiler and one computer, you will be surprised.

~~~
Ididntdothis
I wonder how many C programmers have experience with writing multi
platform/compiler code. I have done a lot of C and C++ but never had to port
it so I would not be surprised if I had made a ton of mistakes.

------
ddingus
I failed the first couple, rapidly getting into I don't know.

Answered the rest, IDK.

Laughed! Great exercize.

------
ummonk
I got #2 wrong, and definitely should have known about integer promotion.

------
pflanze
What are the best strategies to cope with the undefined areas in C?

~~~
saagarjha
The best one is to not write C. The second best is probably to read the
standard papers, be _very_ careful (perhaps using a secure style guideline)
and making generous use of tooling to help you catch mistakes.

------
raviolo
Could someone explain why in question 5

i = 0; return i++ + ++i;

Could produce different results?

~~~
detaro
There's explanations at the bottom after you click the button.

~~~
raviolo
I read it but I still don’t understand. Isn’t it 0 + 2 vs 1 + 1? How would it
produce something other than 2?

~~~
JKCalhoun
How do you get 0 + 2?

I could imagine the post-increment happening after the sum: giving 0 + 1…

~~~
dahart
That’s what I imagined, and what I get with Cygwin gcc, the result is 1. I
thought that the post-increment was supposed to always only happen after the
“statement”, but I was wrong. Other compilers, like gcc on Ubuntu return 2.

This looks like a good explanation of why both are correct and why it’s
confusing:
[https://stackoverflow.com/a/4445841](https://stackoverflow.com/a/4445841)

------
throwaway5153
My answer to a lot of these questions is "If you write code like this and
check it in to our corporate repository, I will cut out your heart and make
you eat it."

~~~
B-Con
Don't forget to include whoever reviewed it.

------
floki999
1/5\. I don’t know C.

------
graycat
j = 1

k = 2

i = ++j+++++k++

i = ?

j = ?

k = ?

------
Ididntdothis
Failed all of them. I totally agree with what the author is writing. Don’t
presume anything but measure. I also tend to code in a way that it’s not
necessary to know all the intricacies. Its not as clever as many like and
often makes for longer code but is usually easier to read.

~~~
wiml
> Don’t presume anything but measure.

I think that's exactly the wrong takeaway here. Most of these have a well-
defined result _on a given platform_ (host + abi). You can measure that
result. But it'll be different on a different platform. And the others don't
have a well-defined result — a real-world compiler will produce _a_ result,
and you can measure that, but it might be different tomorrow. Unless you know
the difference between platform-specific behavior and undefined behavior, you
don't know which ones to avoid.

------
marksmith2996
The real answer is, all 5 questions don't compile so they don't return
anything.

~~~
saagarjha
What compiler are you using?

------
adamnemecek
The tone is insufferable, the test is beyond useless.

~~~
saagarjha
> And at this point, I only have to apologize. The test is clearly provocative
> and may even be a little offensive. I’m sorry if it causes any aggravation.

------
satyenr
What a lot of people don’t get is that it’s not pointers or manual memory
management or even lack of language level support for “modern features” like
object oriented programming, exceptions etc. that make C a pain to use. No it
is the undefined and implementation dependent behaviours. There are simply so
many of them that even experienced C programmers may, at times, run into
trouble.

C is essentially a portable assembler. It’s not enough to learn the language,
you need to have deep understanding of the underlying hardware and compiler
infrastructure.

------
microcolonel
In practice, things in C are not as undefined as the ISO working group
specifies them. It is virtually inconceivable that a mainstream compiler stack
would do anything other than what you'd expect with example four. As for
struct alignment, that's something that most C programmers should know is
implementation-defined (which is one of the reasons we even have _sizeof_ to
begin with, apart from the mere convenience of it).

Sure, you've made your point, but you've made it in a ham-fisted way which
doesn't really help people understand why a given undefined or implementation-
defined behaviour is the way it is, and what things they should verify about
the implementation in order to predict where their code will not work.

~~~
ghettoimp
I think this objection boils down to your perspective on what we mean by "C".

It's reasonable for some folks (especially working programmers who need to
"get stuff done") to think that "what a reasonable compiler in their problem
domain" would do is what "C" means. It's equally reasonable for other folks
(especially compiler writers, verification experts, researchers, etc.) to
think the ISO standard is what "C" means.

It would be great for the standard to be more "reasonable" and have less
undefined behavior. But I, for one, cannot think of a more horrible, thankless
chore than actually trying to make that happen. So much code is written in
"C", and there are so many compilers and platforms, modern and legacy, that
"C" runs on, each with their own notion of "reasonable," that it will take an
incredible amount of work.

~~~
umanwizard
C compilers predate the C standard by many years, so “C is what my compiler
does” is certainly a valid perspective.

Also, the vast majority of programming languages don’t have standards at all,
and people still use them productively.

~~~
pjmlp
Examples of languages with a standards document, describing language semantics
and standard library, not necessarily an ISO one.

Java, JavaScript, Modula-2, Pascal, C++, Fortran, C#, F#, VB.NET, Eiffel, D,
Ada, Common Lisp, Scheme, Python, Scala, Haskell.

------
FpUser
It is an unsavory set of questions that has no bearing on practical work, also
implementation dependent.

The answer is: No, I do not know C or any other language for that matter. I
know some implementations of various languages just enough to write sound and
readable code. And as I forced to use a bit more languages than I like I rely
on local/Internet search to keep my brains concentrated on accomplishing the
actual task rather then effing them up trying to figure out some esoteric
constructs.

~~~
rayiner
The questions are a bit cute, but they’re not testing obscure corner cases of
the language. It’s not like asking if you have the trigraphs memorized. It’s
testing fundamental rules about how C works: overflow, integer promotion,
order of operations, memory layout, etc.

~~~
viraptor
They're all obscure in a "I wouldn't write code like that or let it go through
review" sense. They're very synthetic cases.

~~~
rayiner
But would you write code that adds integers of two different types?Then you
need to know the integer promotion rules. And this is just us just a way of
testing that.

------
kristopolous
This is a perfect example of what I hate about some tests.

Didn't quite know the purpose of the test, there's a difference between code
for any machine and any compiler and gcc running on some vanilla x86, which is
pretty common, and could have been the content (ex: you say it's undefined,
that's obvious, everyone knows that already, but it's still deterministic...
Here's a breakdown of what happens in practice, blah blah blah). There's a
real difference between the kind of "knowing" here and say the kind of
"knowing" with unallocated pointers.

If it said "esoteric implementation on exotic hardware" then it's easy, you
know what they are trying to do. If it said C89 you also know what's up. But
how it's presented, it's a guess.

This was endemic throughout schooling. Instructors would say "just do your
best" and I'd be like "wtf? There's like 2,3, maybe 4 perspectives on this
with different answers depending on how clever you're trying to be or what
you're trying to get at... Might as well put "I'm thinking of a number 1
through 5" on the exam".

~~~
seba_dos1
You can easily get bitten by at least one of those examples just by compiling
your application written on x86 to ARM to get it running on Android, so not
sure if it's as esoteric as you think.

~~~
FartyMcFarter
Or on different compilers, or the same compiler with different options, etc.

~~~
Nullabillity
Or a new release of the same compiler.

------
ioquatix
On a typical modern arch, there are consistent and reasonable answers:

1\. 8 ([https://godbolt.org/z/Udcuj0](https://godbolt.org/z/Udcuj0))

2\. 0 ([https://godbolt.org/z/kZz1YQ](https://godbolt.org/z/kZz1YQ))

3\. -96 ([https://godbolt.org/z/1Xq_Oo](https://godbolt.org/z/1Xq_Oo))

4\. 1 ([https://godbolt.org/z/MIo0s_](https://godbolt.org/z/MIo0s_))

5\. 2 ([https://godbolt.org/z/J0dsVW](https://godbolt.org/z/J0dsVW))

I agree, you can argue that it's unspecified, undefined, or whatever. It might
not be well defined by the C specification, but none of these programs produce
surprising output. Programming in the real world requires that you are able to
read and write code like this, even if it requires that you investigate (and
depend) the specific behaviour of your compiler/platform.

~~~
seba_dos1
Switch the compiler to ARM, which is as "typical modern arch" as it gets, and
see for yourself.

~~~
ioquatix
I don't think it's surprising when you switch machine architecture and/or word
sizes that you get different results. In fact for me, that's completely normal
and to be expected.

~~~
H8crilA
I sure hope you're not writing the libraries that I use in C/C++ :)

