14 ms·
So you think you know C?
- TillE 10y agoThe lack of specified type sizes is something that bit me recently. On pretty much every x86-64 C/C++ compiler, sizeof(long) is 8, except on MSVC where it's 4. Thank goodness for explicitly sized, now-standard types like int64_t.
- tptacek 10y agoThey're standardized now, but they've been in universal use since before I started writing C in 1994.
- Jtsummers 10y agoI wish they were in universal use. I'm constantly cleaning up a colleagues code where he's made many (now incorrect) assumptions about what "int", "short", "long" would mean. Even pointing him to the standard didn't help, I just got back his stock answer, "It works on X so why do we need to worry?" To which I reply, "Because we've changed architectures and compilers since then and now your code is frequently broken." You'd think he'd learn after a while, but some people are too stubborn.
- AnimalMuppet 10y agoI started when sizeof(int) = 2, sizeof(long) = 4. I've seen int go from smaller than a long to equal to a long (both 4 bytes), and then back to not equal (sizeof(long) = 8). And if that colleague won't learn, that sounds like an issue in need of a bit of active management...
- Jtsummers 10y agoUnfortunately he's got the ear of the management. He's a good guy, and actually a good programmer. He's one of those folks that can churn out 1000 lines of (mostly) functioning code in a day. It's not well-written, but it does the job and that's what management wants to see (bad management, but hard to fight). But he's lousy about not learning new things and being stuck in his ways. Once an idea enters his head it's hard to dislodge it. He learned programming from a mediocre CS school (despite the expensive price tag, which they deserve for their medical and law schools, not their engineering disciplines). And when something works, it's hard to convince him that it's wrong (that is, correct by coincidence) or unstable (in this case, sizeof(int) and related issues). He isn't technically (one of his favorite words) wrong in many instances, but problems will arise when we change OS, compiler, or CPU. I'm slowly converting him to seeing things my way. My main contribution has been to mentor the junior developers. Things will change as their contributions worm their way into our projects.
- pjmlp 10y agoI surely don't remember using them while creating code across MS-DOS, Xenix, DG-UX, Windows 3.1, just constraining myself to the compilation targets I used in 1994.
- tptacek 10y agoI didn't do cross-compiled WinAPI / DG-UX in '94, but as luck would have it I did do DG-UX (the first platform I wrote security code on), and I definitely had bit-width types to use.
- pjmlp 10y agoFor Xenix/DG-UX I was only doing standard command line tools, from MS-DOS, and I don't remember seeing any of those. Maybe I was just too ignorant and was limiting myself to what Turbo C had to offer on the MS-DOS side. Of course on Windows we had all those WORD, DWORD, BYTE, BOOL,... macros already, but I was only thinking about actual C standard bit-width types.
- marcosdumay 10y agosizeof(long) >= 4 sizeof(int) >= 2 sizeof(long) >= sizeof(int) That's all you can expect.
- klodolph 10y agoYou literally cannot expect the first two. On word-addressable hardware, you could easily get sizeof(long) = sizeof(int) = 1. This happens on DSPs.
- deleted 10y ago[deleted]
- deleted 10y ago[deleted]
- marcosdumay 10y agoYes. Didn't think about machines where the memory isn't composed of bytes. The best approach is still to not expect anything.
- vortico 10y agoI understand some implementations might break this rule, but what standard states this?
- jcranmer 10y ago(Derived from C11, specifically N1570). §5.2.4.2.1 CHAR_BIT is at least 8 USHRT_MAX is at least 2^16 - 1 UINT_MAX is at least 2^16 - 1 ULONG_MAX is at least 2^32 - 1 ULLONG_MAX is at least 2^64 - 1 §6.2.5p8 guarantees that integers of increasing conversion ranks obeys subset relations, and §6.3.1.1 lays out that short, int, and long have conversion ranks in that order. Thus, sizeof(char) = 1 ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long).
- loeg 10y agoAll of these were present in C99 as well.
- krylon 10y agoTo me, stdint.h was quite possibly the best thing about C99.
- peapicker 10y agoIBMi (OS/400) sizeof long is also 4 by default. Not really "64 bit" tho-on IBMi pointers are 16 bytes with teraspace enabled.
- tptacek 10y agoThis is pretty silly. Being fluent in C has not much at all to do with language quirks. Since (I assume) the 1980s, the comp.lang.c crowd has been reminding us that compilers are fundamentally unknowable and that sizeof(char) could very well be a random walk averaging 1.35. Meanwhile, in the real world, network and kernel programmers have been using some variant of u_int32_t since forever to ensure that they can parse network packets and driver data structures by copying or even casting things into structs. Also, why would anyone write ' ' * 13? For most of these questions, if your first instinct is "that doesn't look like good C code", your answer is better than the "right" answer.
- paxcoder 10y agoActually, since C99, sizeof char is defined to be 1 (byte). The number of bits in a byte is implementation-defined. However, "it's implementation specific and it's most probably this, or that if you use such and such a compiler flag" is different from "I don't know". As a rule of thumb, don't assume things about C's abstractions, read the standard instead, or ask friendly humans who do if they check out.
- ohyoutravel 10y agoIs this true? I thought the C99 standard just specified that the guaranteed minimum size is 1 byte. I've been using CHAR_BIT to define this.
- eps 10y agosizeof(char) is indeed 1. It's the CHAR_BIT that fluctuates (and we have a Cray compiler to prove it).
- ori_b 10y agoNote that POSIX defines CHAR_BIT as 8.
- plorkyeran 10y agosizeof(char) is inherently 1 because sizeof()'s unit is chars, not bits or bytes.
- hal9000xp 10y agoIn 2008, I've started to learn C language by solving these amazing puzzles: http://www.gowrikumar.com/c/index.php http://www.gowrikumar.com/c/index.php I think this page contains high quality C puzzles. If you know something similar, let me know, I would love to solve.
- kristianp 10y agoI used to enjoy these: http://www.gimpel.com/html/bugs.htm http://www.gimpel.com/html/bugs.htm BTW (apologies), the correct past-tense is "In 2008, I started to learn the C...".
- pksadiq 10y agoA note: 'int main()' and 'int main(void)' are different in C (but the same in C++). Most online puzzles uses the former (I have also seen this in hacker rank), but the standard says that the main function should be defined as void if no argument is accepted (or some implementation defined manner)
- 72deluxe 10y agoYep, we write C++ at work and the technical director loves writing (void) in parameters due to years of habit after writing C. To me it looks redundant (I went straight to C++, skipped C).
- maxxxxx 10y agoThis is a good reminder that once you deal with different compilers and operating systems a lot of assumptions don't work anymore.
- cjensen 10y agoReal world things in C that may bite you: (a) char may be signed or unsigned so char a, b; ... short c = (short (a) << 8 | b); will not work to construct a 16-bit value from two 8-bit values. (b) NULL is not a useful macro. For example, printf ("%p", NULL); can have unexpected results. Those things mentioned in the article? Outside of DSPs and GPUs, you can ignore them.
- problems 10y agoYup, I deal with the unsigned char issue at work all the time, I've got a pile of code with casts to unsigned all over the place due to decoding some legacy data formats. I'm not sure what the cleanest way to fix it is, but I find myself writing nasty stuff like: (((short)((unsigned char)a)) << 8) Which works but goddamn is it ever hard to read. Bracket highlighting makes it just bearable. Fortunately I've abstracted this kind of stuff out as much as possible so I only have it in a few places, but it still bites me in the ass sometimes. Curious if anyone has a cleaner way of expressing this?
- cjensen 10y agoDefinitely use the new stdint.h types[1]. That will make your intent a lot clearer. Also consider creating some macros for all common operations (like creating a uint32_t out of four uint8_t). [1] http://en.cppreference.com/w/c/types/integer http://en.cppreference.com/w/c/types/integer
- loeg 10y ago> b) NULL is not a useful macro. For example, > printf ("%p", NULL); > can have unexpected results. How do you figure?
- cjensen 10y agoAccording to the fine standard, an implementation may #define NULL as either #define NULL ((void *) 0) or just plain #define NULL 0 The latter works correctly because a literal 0 in pointer context is interpreted by the compiler as a null pointer. If it is 0, the printf() provides no pointer context, so the 0 is interpreted as an int. That works... poorly if sizeof (int) < sizeof (void *)
- dep_b 10y ago> And I had to learn it the hard way too. I'm worried. What blew up?
- vortico 10y agoWell, the author is a Ukrainian nuclear engineer. ~
- wyldfire 10y agoIMO the real answer is to validate any assumptions by using UBSan wherever possible. Many compilers and many targets are supported. Even if by some chance half of your C team knows all of the ins and outs, subtle things like this can slip by even when under review.
- netule 10y agoThis reminds me of a presentation called "Deep C (and C++)" (2011): http://www.slideshare.net/olvemaudal/deep-c/ http://www.slideshare.net/olvemaudal/deep-c/
- Nadya 10y agoI was about to post something similar, CTLR+F and found this post. :) "Deep C" illustrates the difference well between people who know C and people who know C. A bit of it is language lawyering, but some of it can be practical - although perhaps specific to certain projects or environments. IMO this sort of knowledge is most practical if you're maintaining legacy software or in specific environments that you would otherwise be making a mistake in. People writing evergreen software or in specific targeted environments don't really need to worry about all these types of "Gotch'ya!".
- posterboy 10y agoTook me a while to notice the pun, I had to get back to say I like the subtlety.
- dxhdr 10y agoI don't understand this critique of an outdated version of the language. Let's see questions written in C99 using stdint types. Dealing with integer widths is a non-issue. Yes, inc/dec operators take extra care. Structure packing requires platform awareness, similar to how endianness can matter for data manipulation.
- kosma 10y agoUnfortunately licenses for outdated versions of outdated compilers for outdated versions of an ancient language still sell for 3995€ a seat - I know because I'm using one right now. Nothing beats good old 8051 in terms of price. :)
- faragon 10y agoC is beautiful.
- kosma 10y agoI did this test as a break from sitting in front of Keil C51. These questions highlight a very important issue: there are platforms and compilers out there that will bite you in the ass if you try to unconsciously apply what you learned while using gcc. Under Keil C51, alignment is always 1. This is legal C. Under Keil C51, int is 16-bit. This is legal C. Under Keil C51, sizeof(pointer) is anything from 1 to 3. This is an extension, but a very popular one. Don't assume that just because you can write crash-free code on x86, you "know" C.
- Gibbon1 10y agoWith gcc on an AVR an int is 16 bits and alignment is 1 as well. That said I ported a bunch of stuff from the AVR to a 32 bit ARM Cotrex and the mostly non hardware dependent stuff stuff just worked. On the other hand stdint.h is an old friend to me. Amusing bit int apple = 74000; // fails on 8 bit machines
- zokier 10y ago> Under Keil C51, sizeof(pointer) is anything from 1 to 3. This is an extension, but a very popular one I think this is the funniest thing in this all. You might cross all your t's and dot your i's, and you still may trip in a pitfall when your compiler/platform vendor happens to actually diverge from the standard. Such is life with decades old language with at least as many implementations.
- kosma 10y agoSuch is the price of using C on Harvard machines. It's even more pronounced on '51 as even the smallest $.5 chip has four (!) different address spaces (direct/register ram, indirect ram, xram and code) - and bigger chips have even more as they support memory banking. The real question is: why doesn't C support Harvard architecture natively, without ugly hacks?
- gue5t 10y agoIt's been years and I still have nightmares about using Keil toolchains.
- mikestew 10y agoIn my case, I'd normally say that Betteridge's law applies. But I did surprisingly well on the quiz. Though I'd agree with the comments on how this is kind of silly. The answer is not always "I don't know" but rather, in some of those cases, "I didn't define my data types well enough to know for sure, depending on CPU architecture and the compiler".
- mark-r 10y agoI think the point was that if you're writing portable code to the standard, you can't know the CPU architecture and the compiler!
- ninjakeyboard 10y agoI don't even know C and I got 100%.
- Kenji 10y agoSee? C is so intuitive : )
- nano_o 10y agoThere is an interesting paper that recently explored the de-facto C standard: Into the Depths of C: Elaborating the De Facto Standards, by Memarian et al. http://www.cl.cam.ac.uk/~km569/into_the_depths_of_C.pdf http://www.cl.cam.ac.uk/~km569/into_the_depths_of_C.pdf
- rekshaw 10y agoDon't know C. I got all the answers write...do I know C?
- yellowapple 10y agoYep. You're now a C programmer. Congrats! Linus Torvalds is happily awaiting your first patch to Linux.
- jcoffland 10y agoI know the correct answers to these questions on Linux with an ARM or x86 and on 8-bit AVRs and that's good enough for me.
- AceJohnny2 10y agoARMv6? v7? v8? AArch32? Aarch64? x86? x86_64? :} I'm working right now on a system that mixes Aarch32 and Aarch64 cores. Communication between these cores has to be rock-solid. Previously, I worked on a DSP architecture that could switch at runtime between a 16-bit more and a 24-bit mode. I still sometimes have nightmares. A particular bug that took months to resolve involved app code that expected one mode and interrupts that went into another. The compiler did the right thing most of the time. Most of the time.
- pksadiq 10y agoDo you know the value of 'i' after executing the following statements (assume it's written inside the main function): int i = 0; int j = sizeof (i++); // Thanks. :-)
- wruza 10y agoZero with great amount of indeedity.
- chetanahuja 10y agoTo all those kvetching about weird C (and by extension, C++) behaviors... what is your alternative for an unmanaged, system level language with broad compiler/library support?
- uryga 10y agoProbably nothing :/ However criticisms can be valid without offering alternatives. (if that's what you were getting at)
- xenadu02 10y agoWe're still fighting to get people to acknowledge that C is a problem. Way too many software engineers are still in denial about that. D, Rust, and Swift are the only languages I know of that have "systems programming" as a focus.
- pjmlp 10y agoAda and Spark are a possibility. You might still try to use FreePascal. If targeting embedded development, there are a few Basic, Pascal and Oberon compiler vendors to choose from. And there is also Rust and Swift if you are happy to stay just on Apple OSes (Open source Swift is still a bit of WIP).
- dwarman 10y agoSharing data structures in RAM between 64 bit and 32 bit and 16 bit architectures brings this home painfully. Data communications is equivalently plainful, possibly moreso becaude one has no knowledge of the receiver's architecture. Sending multi-byte values between big- and little-endian machines, for example. The test could also have included questions on things like packing bools and enums into structures. Again critical to get righht when sharing or transmitting structs, or anything bigger than a char.
- 3r1kB 10y agoYep, unfortunately a universal 'Network-Byte_order'[1] stil doesn't exist. Things like this were especially a pain with industrial network protocols that we used in our applications during the transition from PowerPC to Intel on OS X. All the CFSwapInt16HostToBig() and CFSwapInt16BigToHost() stuff... And earlier before I learned about '#pragma packed' it took me a while to find out where all these extra zeroes suddenly came from :-D [1] https://en.wikipedia.org/wiki/Endianness#Networking https://en.wikipedia.org/wiki/Endianness#Networking
- golergka 10y agoCorrectly answered all the questions. I'm not C expert, by far; I only have an instinct that if it's about C and memory layout, I shouldn't rely on my understanding and check it instead. On every architecture I'm targeting.
- OMGWTF 10y agoWould question 4 have a different answer if it was: unsigned int i = 16; return (((((i >= i) << i) >> i) <= i)); ? (I changed `int` to `unsigned int`.)
- guyzero 10y agoNope, on 16-bit platforms a 16-bit left shift has a very different result from a 16-bit left shift on a 32 bit platform. It's basically a question about knowing when you'll get burned by the size of int varying.
- OMGWTF 10y agoSo? (i >= i) // 1 ((i >= i) << i) // 0x10000 or 0 (((i >= i) << i) >> i) // 1 or 0 (((((i >= i) << i) >> i) <= i)) // ((0 or 1) <= 16) == 1 Where did I go wrong?
- loeg 10y ago> ((i >= i) << i) UB.
- OMGWTF 10y agoAh, you're right. > If the value of the right operand is negative > or is greater than or equal to the width of > the promoted left operand, the behavior is > undefined" (http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1570.pdf http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1570.pdf - ISO/IEC 9899:201x (C11 Working Draft N1570): 6.5.7 Bitwise shift operators)
- loeg 10y agoYeah. It seems like something easy to define to me, at least in the case the compiler can recognize the shift is a constant. But that's the standard for you.
- 10y ago
- te_platt 10y agoI'm reminded of an old story about a man trying to fill a job for driving a stage coach. He asks each applicant how close they can drive to the edge of a cliff without going over. The first few applicants brag about being able to right up to the edge. The job goes to the first to respond "I don't know, I try to stay as far away as possible".
- tome 10y agoIt seems the padding one is wrong for an even simpler reason. You don't know what sizeof(int) is.
- mlvljr 10y agoAnd this comment is buried of course (was looking for it). Cheers from the [dead] :)
- davidgerard 10y ago> That’s right, Mozart also wrote in C. applause
- sickbeard 10y agoWe teach people to write proper code, then they get interviewed on the most dumb half-assed code that nobody writes in the real world because we like mental gymnastics
- bogomipz 10y agoI enjoyed this post. I am curious do any of you have any C-specific blogs that you read regularly? Maybe not exclusively C but predominantly C or mostly C-related?
- awalton 10y agoIf you write a quiz like this, and "Don't Know" is an answer but "Undefined Behavior" and "Implementation-Dependent" are left off, you're doing it wrong.
- simosx 10y agoPeople would figure it out from the start if "Implementation-Dependent" was an option. Perhaps a "Something else" would be more appropriate so that the reader would still be surprised at the end and feel it was a decent multiple-choice test.
- sqeaky 10y agoIt is likely that the state of someone's knowledge is "don't know" regardless of the actual state of affairs. If a person wanting to improve guesses correctly, did they actually learn anything? Can they use that next time? They definitely can if they get it wrong and have a wrong answer to start research with. Edit - I see that I misread your comment, please disregard this. I thought the quiz had don't know and the other options you specified.
- acveilleux 10y agoOr perhaps you're not a native english speaker?
- tedunangst 10y agoIndeed. It's not like the answers are unknowable.
- kmill 10y agoThe question is "what would the return value be," and without knowing which compiler is used you don't know what it would return. It doesn't make sense to say the program returns "undefined behavior" or "implementation-dependent." You might be taking issue with the question itself, but I think it's a valid question because C compilers tend to be lenient with what they compile.
- gkya 10y ago
- Kenji 10y agoI knew it was Undefined Behaviour, but I played along and tried to guess what it would return on GCC/Windows/x86/no optimisation, in the assumption that the author is clueless about UB and just ran some tests and did a quiz about it.
- Sir_Cmpwn 10y agoI realized these were trick questions at #5. My answers till then were "well, on gcc on i686, probably ____". The trick questions are in poor taste imo.
- acveilleux 10y agoI would've thought the first question was a big red flag but I guess in Windows and x86 linux int is 32 bit even with 64-bit processors. Not the case in MIPS64 (under IRIX) or on DEC Alpha. I had my formative C experience in the DOS era with 16-bit compilers and then SGIs with 64-bit compilers (and I had the "fun" of porting grad student written code from SGI to Linux) so you get burned with expectations about int length. Packing makes it even worse as many RISC machines (like MIPS) are word addressed and non-aligned loads require a bunch of bit fiddling or you get a Bus Error (SIGBUS).
- smegel 10y ago> Yes, the right answer for every question in “I don’t know”. Do you think you know the English language?
- bnegreve 10y agoAnother C quizz, (which I find a little bit more interesting): Will it optimize: http://ridiculousfish.com/blog/posts/will-it-optimize.html http://ridiculousfish.com/blog/posts/will-it-optimize.html edit: it's actually about gcc rather than the standard itself, still pretty interesting though.
- bjterry 10y agoI sort of understand the point of this post, but I basically disagree with it. From a pedantic perspective, you aren't writing "good C" if your code isn't 100% standards-compliant, but if we applied this same rigor to dynamic languages, no one would get anything done in them. How do you know your Ruby code works? The community has settled on the following solution: because it passes thousands of tests on every aspect of every function. Extending that to C code, your code works if it passes your tests. Test on every platform and every compiler you support, or it's not going to work. Avoid undefined behavior, sure, that's probably good practice. But there is no need to bend over backwards for compilers from the 90s targeting the AS/400 if you are writing a linux application that only needs to support Ubuntu 16.04. If your only means of avoiding broken behavior is "we're going to try really hard not to do anything undefined or implementation-defined" you will fail. But if you pass your functional tests, your user doesn't care whether you are invoking undefined behavior with every function call.
- Cyph0n 10y agoWell said. I'd also add that most of these questions seem aimed at people who've written C compilers before, at least to me. My guess is that even an expert at C like antirez wouldn't really care about such nitpicky issues while writing Redis. Porting Redis to some arcane architecture is a different story entirely however.
- nialv7 10y agoWell I would agree that undefined behaviors are unavoidable. Indeed, if you insert checks into your code to completely rule out UB, that will have a huge impact on performance. On the other hand, I don't agree passing tests is enough. No matter how many tests you run, you can't guarantee there's no bug in your code. This is important especially when security is a critical requirement.
- paulftw 10y agoWhile I get the overall message I'm not sure last question is that unpredictable: I++ + ++I If postfix is executed first it returns 0 but prefix returns 2 and the result is 2. If prefix is executed first it returns 1 and then postfix also returns 1, result of the addition is again 2. What else is ambiguous about this snippet? I can't think of a parse tree that'd evaluate addition before any of the increments - that'd be a syntax error. If the code were (++i + i++) there could be alternative interpretations, but again ++ requires lvalue...
- loup-vaillant 10y agoThe order of evaluation in such an expression is entirely unspecified. There are points in C programs that separate a "before" and an "after". The semicolon is a typical such point. There are others. Now between two adjacent points anything goes. In this particular example the evaluations and effects are allowed to be interleaved: pre = I + 1; // evaluating the pre-increment post = I; // evaluating the post-increment I++; // effect of pre-increment I++; // effect of post-increment return pre + post; // evaluating the sum Simply put, pre and post-increment are allowed to occur "simultaneously".
- mlvljr 10y agoThis is not (just) unspecified, but rather a classic UB.
- thick18cm 10y agowhat you are saying may be correct, but you are "overloading" the term "order of evaluation" to encompass atomicity (atomicity of operations like i++); is that kosher?
- loup-vaillant 10y agoIndeed I am. What I want to convey is, between 2 sequence points, C is actually non-strict. Any effect is like unsafeInterleaveIO from Haskell. Which is why I rarely mix up effects together or with a computation in a single instruction.
- pipio21 10y agoThe right answer to all this questions is: "Nobody in their right mind should use code like this in production". You should avoid code like this at all cost. If you want to use this code because you feel smarter, you will be creating enormous amount of problems in the future and you are actually dumb. The way to win the game is not playing it. Specially if you are programming nuclear plants. If you see this code what you have to do is replace it, not understand it. It works today but when you change the compiler or the architecture 10 years from now, it will be such a huge pain in the ass to find all the bugs and undefined behavior it creates.
- HelloNurse 10y agoAnd the key to not playing this undefined behaviour game is using integers of known size. C has them since C99, so using int, short, char etc. like in the article is reckless and inexcusable.
- user5994461 10y agoHow to recognize a terrible quizz in any programming languages? Simple. It keeps asking code questions about undefined behaviors that MUST NEVER be used in real world programming. #StandUpAndLeaveTheInterview
- deleted 10y ago[deleted]
- shmerl 10y agoSo the author thinks undefined behavior is the esoteric knowledge of C?
- blt 10y agoIMO, this is actually a fairly good test of thorough C knowledge. All of these issues are regularly described in articles, blogs, forums, HN posts, etc. Anyone who has programmed C for a few years and put in effort to master the language should know this stuff.
- dreta 10y agoTHIS is why C is bad. It’s not because there are no classes, and you have to manage your own memory, or because it has unsafe pointers. It’s this. And it’s the same reason why C++ is awful even before you get to the "++" part. I wish that by now we had a proper ASM abstraction language that allows the programmer to work with the architecture they’re programming for, instead of all the “undefined behaviour” we have to deal with since the 70s.
- kilon 10y agoYou confuse knowing with expertise.
- chris_wot 10y agoEven when you know the standard, you will still get undefined behaviour.
- ambar123 10y agoThats ultimate question i have ever been through..