88 ms·
How to C in 2016
- golergka 11y agoAs someone who has a been trying to get into modern C from higher level languages and looking for more modern sources than K&R, THANK YOU. (Even if you just posted the link here and have no relation to original author). (Of course, this post is not the only place to learn about modern C, but still, a very useful one).
- J_Darnley 11y agoYou might also find 21st Century C published by O'Reilly useful. It has a few chapters introduction to a "modern" development environment. Then it covers the main points of newer C99 language and syntax. I found it quite interesting as an beginner/intermediate programmer. It has a few things I dislike, particularly its chapter on autotools (not exactly a modern thing).
- joshvm 11y agoThere's 21st Century C by O'Reilly, which I enjoyed. I'm not qualified to say whether you should read it, but there seems to be a lot less backlash towards it than, say, Learn C The Hard Way. My main criticism is that the author wastes half the book on good development practice which is all well and good, but not if you're just there for the C. For developers not familiar with make, testing and so on, it's great. As for the actual C content, frankly this was where I went to when I was wondering how to write better (modern) C, so I don't really know any better. The writing is almost certainly opinionated and as above, could be a lot more comprehensive. (oops edit window left open for a while, see sibling!)
- tines 11y agoMost of this advice is pretty standard, but this stood out: > You should always use calloc. There is no performance penalty for getting zero'd memory. But isn't there? This may be true the first time the program requests a new page from the OS, but when your program starts reusing memory there's going to be a performance hit.
- nappy-doo 11y agoYes, but it's more subtle. The argument that there's no performance different between malloc/calloc is only valid if you have an MMU that will zero pages. Lots of embedded systems don't have such MMUs (or even an MMU at all); therefore, there is a difference between malloc and calloc.
- deleted 11y ago[deleted]
- bnegreve 11y agoBut desktop processors also don't zero pages, do they? And by zeroing pages, you prevent lazy memory allocation policies which do not allocate the pages until they're read or written for the first time. This can have a significant impact on memory usage, data locality etc. So I'm quite skeptical that there is no difference between calloc and malloc. Is there more evidences of this somewhere?
- kqr 11y agoHow does it prevent such allocation? Can you not just mark the first read from an area to always return 0, and then allocate as soon as it happens? That also has the benefit of not having to zero anything if you're only writing to it.
- DSMan195276 11y agoInternally, an OS like Linux maps every page in a requested piece of memory to a read-only page of zeros. When you attempt to read from this page, it works fine. When you attempt to write to it, the MMU causes a page-fault and the OS inserts a page where the zero-page is, and then continues your program execution. Thus, it doesn't actually have to allocate any memory for you if you never write to the new pages. But the OS/MMU doesn't distinguish between a regular write, and a write of zero. Thus, if you manually zero every page you get (And thus write zeros to the read-only zero-page), it'll page-fault back to the OS and the OS will have to allocate a new page so that the write succeeds - Even though if you didn't do the zeroing of memory you would have gotten the same effect of having a bunch of zeros in memory, but without having to allocate any new pages for your process.
- csvan 11y ago"The first rule of C is don't write C if you can avoid it." This ought to carved into the walls of R&D departments everywhere.
- chrisseaton 11y agoWhy do you think R&D departments use C more than anyone else?
- qntty 11y agoI think the implication is that R&D departments have the control to avoid it
- jballanc 11y ago"The only bug free code is the code you don't write."
- jandrese 11y agoIf you think the third party library is bug free I have news for you. The only difference is that you get to debug someone else's code instead, and you might not have the source.
- tsavola 11y ago> LTO fixes the "source analysis and optimization across compilation units problem" by annotating object files with intermediate representation so source-aware optimizations can be carried out across compilation units at link time (this slows down the linking process noticeably, but make -j helps). make -j doesn't help linking speed, unless multiple binaries are begin built.
- delinka 11y agoIt's not the linking process that make -j speeds up, but the optimization part.
- tsavola 11y agoAFAIK the bulk of link time optimization happens in the linker. The parallelizable compiler invocations just store extra information in the object files. The linker invocation is a single make job, which is the unit of make -j parallelism.
- deleted 11y ago[deleted]
- lorenzhs 11y agogcc has `-flto=n` to use n cores for the link-time optimization. clang doesn't like that syntax though, but I think it does parallel LTO automatically.
- mapleoin 11y agoWriting correct code at scale is essentially impossible. We have multiple operating systems, runtimes, libraries, and hardware platforms to worry about without even considering things like random bit flips in RAM or our block devices lying to us with unknown probability. The best we can do is write simple, understandable code with as few indirections and as little undocumented magic as possible. Applies very well to all programming.
- userbinator 11y agoI've noticed that "low-level" languages like C, or for a more extreme example, assembly languages, tend to encourage simpler code if for nothing other than the fact that everything takes many more lines than in higher-level languages. E.g. if you want to do OOP you'll have to allocate memory and call constructors manually, pass explicit 'this' parameters into all method calls, declare virtual function tables, etc. Certainly there are cases where this tempts too easy solutions like fixed-size overflow-prone buffers instead of dynamically allocating strings, but on the other hand it forces you to think more about whether you actually need to do something ("should this string ever be more than X bytes long? Maybe not, might as well use fixed-size array - but I should not forget to check that length!"), which I think is overall a good thing.
- mrich 11y agoIt also makes you think upfront about the design much more. With all the intricacies of writing correct low-level code you really want to write it once only and not try out different designs and refactor between them, where mistakes quickly happen. Of course this is a disadvantage if you don't have sufficient experience in the problem domain. Sometimes I resort to writing prototypes in a script language in that case, translating the best design to the low-level language.
- adrianN 11y agoA language that is "write it right the first time, refactoring is painful" is a poor fit for most problems. Requirements change, if your software can't be adapted easily your project will fail. The general failure of waterfall-style software development is a strong indicator that finding the "right" design up front usually doesn't work out.
- ue_ 11y ago>If you find yourself typing char or int or short or long or unsigned into new code, you're doing it wrong. int is a type that's at least 16 bits large. I can't see the problem with using it. If I specify that I want a type that's 32 bits large, my code won't run on 16 bit systems. Why should I assume? Because the author doesn't believe in writing code that works on "20 year old systems"?
- officialtedleo 11y ago> If I specify that I want a type that's 32 bits large, my code won't run on 16 bit systems. Why not?
- ue_ 11y agoNevermind, I'm wrong. I've read that if one uses int64_t with a compiler that targets a 32 bit system, doing various operations on the int64_t variable will be much slower because more machine code instructions need to be generated. The point stands: Why not use int if one is looking to target any architecture that C will run on?
- Gankro 11y agoIf 16 bits is enough, use an explicitly 16-bit integer. If you want something architecture-sized (e.g. because it's dealing with pointers/addresses), use things like `size_t` which makes the intent much more clear, and which are better defined. In most places, if you're using an `int` for non-address-y stuff you're just wasting space on wider archs, or you're overflowing on less wide ones. The only exception off the top of my head is "I have an array of bytes, but want to bulk process more efficiently than u8 would allow". Things like `size_t` (or explicit SIMD types!) are still probably a better choice there, if only to simplify the rule to "just don't use int".
- nwmcsween 11y agoYou don't know how hardware works...
- plg 11y ago"Notice we don't have char anymore. char is actually misnamed and misused in C." so defining a string happens how if not: char *my_string = "Hello";
- adsche 11y agojust read on to the next paragraph...
- efaref 11y agoThat's not even correct. The type of string literals is "const char *".
- pbsd 11y agoTo be pedantic about it, string literals in C are of type `char[]`. In C++ they are of type `const char []`. In either case, altering the contents of the string literal is undefined behavior.
- plg 11y agoi didn't mean a const i meant a variable
- julian-klode 11y agoYour variable my_string is a pointer to a constant string. char * my_string It must be a const char * my_string You can still assign a different string to that, as my_string is not const. For my_string to be const, you'd have to use char * const my_string Now the location pointed to by my_string is writable but I cannot change the location. To make it entirely unmodifiable, you'd use const char * const my_string. On the other hand, it might make more sense to use an array here. const char my_string[] = "hello";
- xedarius 11y agoI don't think the claim of zero performance penalty for calloc is true. If for example you're calling calloc on a game console it is 100% not true.
- mrich 11y agoQuite concise and short. Now wouldn't it be great to also have such a short guide for C++... (thinking about the tome that is https://github.com/isocpp/CppCoreGuidelines https://github.com/isocpp/CppCoreGuidelines)
- solidangle 11y ago> The rules are designed to be supported by an analysis tool. Violations of rules will be flagged with references (or links) to the relevant rule. We do not expect you to memorize all the rules before trying to write code. But I agree, a short guide for C++ with the most important stuff in it would be nice.
- ausjke 11y agoIt will be great if there is a similar guidance for C, i.e. CCoreGuidelines.
- nwatson 11y agoMy head's been mostly in Java and Python the last three years, and my old work's C++ practices were woefully out-of-date. I really appreciate the work put into the CppCoreGuideline documents ... it's real complex, though. I was dreading getting back to C++ with the Rule-of-3 and now the Rule-of-5 -- what a pain! Fortunately I ran into the Rule-of-Zero as described here: http://accu.org/index.php/journals/1896 http://accu.org/index.php/journals/1896. The CppCoreGuidelines have a more restrictive Rule-of-Zero but allude to the link's form . . . the only way I'll get back to C++ is if I can avoid move-constructor and move-assignment for the majority of my work ... and the link provides the answer!
- Sir_Cmpwn 11y ago>The first rule of C is don't write C if you can avoid it. Man, this is just lame. C is a really nice, useful language, especially if you learn the internals. It requires discipline and care to use effectively, but it can be done right. The problem is that a lot of C programmers only know C and can't apply higher level concepts or abstractions to it because they've never learned anything else. This makes their code harder to maintain and easier to break.
- adrianN 11y agoI'd argue that it can't be done right for non trivial programs without enormous effort. Even software written by expert C programmers with years and years of experience contains bugs that are caused by C's unsafe features.
- workitout 11y agoI'd agree, there was a great book called Deep C Secrets by Peter van der Linden. The book itself is not without errors but still provided some useful insights to things I didn't realize I was doing wrong back when I coded in C.
- lostcolony 11y agoHe's not saying that C isn't useful, but that for most problems it's overkill. Why inflict the extra burden on yourself -for no benefit-? Most code being written can be done just as well in a language with garbage collection, that has more powerful abstractions and paradigms as first class citizens...why not take advantage of them? Of course you can write those higher level concepts/abstractions in C, but the point is, -they've already been written-, tested, and documented, by other people. Why write untested, undocumented code that reimplements those things when you have no compelling reason to?
- kqr 11y agoThere are even languages without garbage collection that makes a lot of things easier than they are in C. See Ada, Rust, ~~Go~~ D and others.
- J_Darnley 11y ago> You continue arguing, "on a 32 bit patform I want 32 bit longs and on a 64 bit platform I want 64 bit longs!" Ha ha ha. What kind of programmer would argue this? Do they ignore Windows 64? The type long is 32 bits there.
- AndyKelley 11y agoThe author linked to this site: https://www.securecoding.cert.org/confluence/display/c/SEI+CERT+C+Coding+Standard https://www.securecoding.cert.org/confluence/display/c/SEI+C... These are wonderful. Concise nuggets of knowledge that you can keep in your head, little warnings of what not to do. Side note, it's really fun reading this article about modern C given that I'm working on an experimental programming language meant to replace C [1]. Just today I got the Guess Number Game example working, without a dependency on libc [2] (caveat: Linux x86_64 only so far). So, I'm having fun trying to think about how C could be better, reading implementations of various libc functions, and writing my own "standard library". For example, in my language, there are no `int`, `unsigned char`, `short` types, there are only `i32`, `u8`, `i16`, etc (I shamelessly stole this idea from Rust). This mirrors the article's suggestion to only use int32_t, uint8_t, int16_t, etc. But, since it's a new programming language, we don't have the other types sitting there as a red herring. [1]: https://github.com/andrewrk/zig https://github.com/andrewrk/zig [2]: https://gist.github.com/andrewrk/131e6f48a15c42fc5ca8 https://gist.github.com/andrewrk/131e6f48a15c42fc5ca8
- WalterBright 11y ago> there are only `i32`, `u8`, `i16`, etc (I shamelessly stole this idea from Rust). Those are very old ideas.
- kibwen 11y agoAnd Rust stole them from very old languages, but you haven't at all refuted the author's statement. :P
- ilek 11y ago>only `i32`, `u8`, `i16` While these are not new ideas - Why not use s32 rather than i32 and so on. Makes more sense if you use something like u32 too. I think it's important when making another language to really question everything you do in it. Why use this operator, or why use this mnemonic for built in types?
- masklinn 11y ago
- kardos 11y ago> Formatting > The solution here is to always use an automated code formatter. Spot on. Python got that part right.
- kqr 11y agoSurely you mean Go? There is one for Python, but it's not official.
- kardos 11y agoPython won't run without specified indentation, and leaves no freedom for brace positioning. On the C side, we've accumulated a variety of formatting styles [1]. [1] https://en.wikipedia.org/wiki/Indent_style#Styles https://en.wikipedia.org/wiki/Indent_style#Styles
- yarrel 11y agoYou can automatically format C. Python, not so much.
- kqr 11y agoBoth of you make the mistake of confusing formatting with indentation. Indentation is just a tiny part of formatting. There are tools to automatically format Python (but they do obviously require that the indentation is somewhat correct before you run them) and they work fairly well. :)
- hyperpape 11y agoMy experience is that Python handles this wonderfully on the reading side, because code will always be consistently formatted. But on the writing side, it's harder (impossible?) to have tools that format an entire file, because without braces, various things could be at different levels of indentation. You have to indent as you go, and changing the level of indentation can be a little painful. In curly brace languages, you can sometimes work very fast by crapping things onto the page and then formatting the entire file. I don't know which I absolutely prefer, but there is a tradeoff to whitespace based blocks. That said, I think C & Java suffer for the combination of optional braces and lack of a standard format tool.
- rwmj 11y agoAfter too many problems with "unfriendly C", I finally gave in and now compile everything with -fno-strict-overflow (the Linux kernel does the same thing). I don't care about architectures that don't use twos-complement.
- deleted 11y ago[deleted]
- caf 11y agoThe article seems to be implying that you should use both -O2 and -Os, but these are mutually exclusive options - if you supply both, only the last one specified has effect.
- _wmd 11y agoSome of this seems like horrendously bad advice: - Using fixed width integers in for loops seems like a fabulous way to reduce the portability of code - the statements in "C allows static initialization of stack-allocated arrays" are _not_ equivalent, one is a bitwise zeroing while the other is arithmetic initialization. On some machines changing these statements blindly will cause a different bit pattern to end up in memory (because there are no requirements on the machine's representations for e.g. integers or NULL or ..). There are sound reasons why the bitwise approach could be preferred, for example, because a project has debug wrappers for memset that clearly demarcate uninitialized data - the statements in "C99 allows variable length array initializsers" aren't even slightly equivalent. His suggestion uses automatic storage (and subsequently a stack overflow triggered by user input -- aka. a security bug) - "There is no performance penalty for getting zero'd memory" this is bullshit. calloc() might optimize for the case where it is allocating from a page the OS has just supplied but I doubt any implementation ever bothered to do this, since it relies on the host OS to always zero new pages - "If a function accepts arbitrary input data and a length to process, don't restrict the type of the parameter." the former version is in every way more self-documenting and consistent with the apparent function of the procedure than his use of void. It also runs counter to a rule from slightly more conservative times: avoid void at all costs, since it automatically silences all casting warnings. Modern C provides a bunch of new things that make typing safer but none of those techniques are mentioned here. For example word-sized structs combined with struct literals can eliminate whole classes of historical bugs. On the fixed width integers thing, the size of 'int', 'long' and 'long long' are designed to vary according to the machine in use. On some fancy Intel box perhaps there is no cost to using a 64bit type all the time, but on a microcontroller you've just caused the compiler to inject a software arithmetic implementation into your binary (and your code is running 100x slower too). He doesn't even mention types like intfast_t designed for this case, despite explicitly indicating "don't write C if you have to", which in 2016 pretty commonly means you're targeting such a device
- Nursie 11y ago>> - Using fixed width integers in for loops seems like a fabulous way to reduce the portability of code Can you explain this? I'm not seeing it. You should generally know the range of numbers you're seeking to store in an integer variable, if you don't then you're asking for trouble.
- lephyrius 11y ago"generally you want -O2" I see this a lot shouldn't O3 produce better optimizations? Is that people have large codebases? OR is it that O3 does some CPU/memory tradeoffs? OR something else?
- Avshalom 11y agowith out RTFA -O3 is where a lot of the weird compiler deciding it's smarter than you optimizations come in, and most of the time it is smarter than you and sometimes it introduces weird behavior. ALSO it generally produces a larger binary which means less of it fits in the cache. I know a while back it was often suggested to try O3 and OS because often OS ended up being faster.
- michael_h 11y agoSometimes -O3 produces slower code than -O2. Sometimes it produces incorrect code. I usually compile with -O2 and annotate certain functions with O3 - if it looks like vectorization will give the function a boost.
- deleted 11y ago[deleted]
- to3m 11y agoOne point about char is that a pointer to a character type (character types are char, unsigned char, signed char - see 6.2.5.15) may alias non-char objects in a standard-compliant fashion (see 6.3.2.3.7). But uint8_t and int8_t may not necessarily be suitable replacements, because they are defined to be integer types (see B.19 - character types are a subset of integer types), and in any event may not even exist (7.20.1.1.3).
- xcgvgh 11y agoI think the whole article needs a preface, assuming CHAR_BIT is defined as 8.
- messe 11y agoThat still doesn't mean that `uint8_t` will be defined as `char`.
- xcgvgh 11y agoWith my assumption, it will be defined as an unsigned char, as it should be.
- to3m 11y agoBut it doesn't have to be - the 8-bit integer types could map to 8-bit implementation-defined integer types instead. Such types would not necessarily to have the same aliasing behaviour as their equivalently sized and equivalently signed character types.
- xcgvgh 11y agoTypedef doesn't create a new type, it merely creates a new name for a type. I don't know what you mean by implementation defined integer type. If the implementation is conforming, then any integer type that isn't a basic C integer type, is really just a typedef for one of the basic C integer types ( char, short int, int, long, ... ). In C, with CHAR_BIT defined as 8, unsigned char is the only type that satisfies the requirements of uint8_t. ( It is also possible that it is defined as char, if implementation defines char to have the same range, representation, and behavior as unsigned char. In this case, char effectively becomes unsigned char, but is still a distinct type. The types are compatible (can alias).) Now why is unsigned char the only possibility. Type uint8_t is defined to have two'2 complement, have exactly 8 bits, no padding, and be unsigned. So we need an unsigned integer type. The unsigned type that follows unsigned char in rank is unsigned short char. But this type is defined to have ranges at least from 0 to 65535. Since CHAR_BIT is 8, unsigned short int cannot be used, because it has to have more than 8 bits. As there is no type in rank before unsigned char, it remains the only possibility.
- atemerev 11y agoOh my goodness, so true.
- Nursie 11y agoUsually I find something very objectionable in these C guides, but I like this one, and it's hit some of my favourite/most hated bugbears. In recent years it's nearly driven me mad seeing people use the old types either directly or re-implementing their own equivalent of stdint.h, complete with all sorts of platform switching and other nonsense. We've got these facilities, they're not exactly bleeding edge (the clue's in the name C99), let's use them!
- stromgo 11y agoThe document recommends "intptr_t" over "long" for system-dependent types, and I've considered doing this but I've been put off by the printing type of %"PRIdPTR" instead of %ld. I wonder if on modern platforms it'd be ok to use intptr_t and the printing type %zd (meant for ssize_t).
- Nursie 11y agoYeah I must admit the printing types are a bit ugly!
- mmphosis 11y ago# cc test.c -o test -O3 -Os -Wall -pedantic -Werror -Wshadow -Wstrict-aliasing -Wstrict-overflow -std=c99 test.c:1:2: error: #import is a GCC extension
- grahamedgecombe 11y agouse -std=gnu99 instead
- zrm 11y agoThe article says not to do this: uint32_t numbers[64]; memset(numbers, 0, sizeof(numbers) * 64); That is certainly true because that is a buffer overrun.
- bluetomcat 11y ago> Use: > ptrdiff_t diff = (uintptr_t)ptrOld - (uintptr_t)ptrNew; The casts to uintptr_t are not needed in this case, as the result of subtracting two pointers already yields ptrdiff_t. Also, the mere subtraction of two pointers that do not point to the same "array object" yields undefined behaviour as per the standard.
- stromgo 11y agoThe casts are needed for "dirty pointer math". Without the casts, &A[1] - &A[0] returns 1, not 4, for an int *A.
- sltkr 11y agoThe code is still wrong for another reason: if (uintptr_t)ptrOld < (uintptr_t)ptrNew, then the subtraction is calculated modulo some power of 2 yielding a large number. The result of converting this to a signed integral type like ptrdiff_t is implementation defined. In practice it works only if sizeof(ptrdiff_t) == sizeof(uintptr_t) AND the system uses two's complement for signed integers. Neither property is guaranteed by the standard. I think the correct way to find the byte offset is this: ptrdiff_t diff = (char*)ptrOld - (char*)ptrNew;
- _kst_ 11y agoAnd it's wrong for yet another reason. You can convert a pointer to uintprt_t, but arithmetic on the result isn't necessarily meaningful.
- shawn-furyan 11y agoThis thread makes me eager to get back into the minimal C programming that I left behind in my college assembly class. ~1 beat~ Yikes! ~leaves self-shaped dust cloud behind as hurriedly runs toward higher [level] ground~
- jhallenworld 11y agoC should have an inferred integer type: for (infer x = 0; x != 10; ++x) ... If the compiler can infer the type, use the fastest integer. If it can not infer, use the largest integer. I'm sure this would create difficulties with structs, but at least for local integers it would be nice.
- bluetomcat 11y agoEven if you use int in that case, the compiler is free to pick the optimal underlying data type (byte, probably), provided that it has proven that the value will never go out of that data type's limits and the program doesn't do anything fancy like casting the int to void * and reading it byte by byte.
- jhallenworld 11y agoInteresting, so it's a waste of time to use anything but intmax_t.
- wyldfire 11y agoint8_fast_t meets this description (because the range for this particular loop only spans 10, but you should use the int16_ or int32_ versions as appropriate). int8_fast_t will likely use native-register-sized integers (probably 32 or 64 bit on many architectures). This is IMO very close to the inference you describe, while still providing some stipulations about what you need. int8_fast_t and its siblings are defined in <stdint.h>, described in this article (though it would be nice if the article mentioned them).
- legulere 11y agoI would add: - Add security measures provided by your compiler to your builds: -fstack-protector-strong on GCC. -fsanitize=safestack on Clang if you have a version that's new enough. - Build the program with AddressSanitizer and run it with typical data. Run it through a fuzzer afterwards.
- roberthahn 11y agoIn light of the fact that best practices have evolved considerably since K&R, what book(s) would you recommend for people wanting to learn C today?
- hyperpape 11y agoI asked on Twitter, and got several recommendations https://twitter.com/hyperpape/status/685476672393605120 https://twitter.com/hyperpape/status/685476672393605120.
- roberthahn 11y agoVery nice! Thanks for your help!
- J_Darnley 11y ago21st Century C (published by O'Reilly) was recommended by myself and one other elsewhere in this thread.
- ThatGeoGuy 11y agoModern C by Jens Gustedt: http://icube-icps.unistra.fr/img_auth.php/d/db/ModernC.pdf http://icube-icps.unistra.fr/img_auth.php/d/db/ModernC.pdf It's not exactly 100% complete in the literal sense, but everything covering the language spec and usage is there. I highly suggest working through it, as the book itself is incredible for going from beginner to expert in the short span it does.
- roberthahn 11y agoThank you very much for the link and suggestion! Much appreciated!
- jhallenworld 11y ago-Wextra is not enough. Should also have: -Wconversion and -Wstrict-overflow=4 In some ways -Wextra is too much: it's annoying that -Wall with -Wextra complain about unused parameters.
- kenOfYugen 11y ago> Standard c99 If you have to write C, why not go with ANSI C/C89 for compatibility/portability reasons?
- _kst_ 11y agoBecause the 1999 and 2011 standards added some useful features. Unfortunately, there are still some compilers that don't support C99.
- vbezhenar 11y agoHow to C in 2016: use valgrind from the start. I think that it's the best tool for C in the world.
- amadvance 11y agoAnd don't forget about helgrind and drd for multithread code. I would not even dare to use threads without the support of these tools. In fact, this seems a side where C has a big advantage over other languages.
- jedisct1 11y agoAnd then, someone points out that your code doesn't work in Visual Studio, and that, of course, you have to support VS < 2015. So, you're back to writing C89, in 2016.
- floatboth 11y ago#ifdef _MSC_VER #error "Do not use Microsoft compilers." #endif
- mrcrassic 11y ago> C99 allows variable declarations anywhere I actually like declaring my variables at the top. To me, this seems more readable than searching for declaration statements throughout the code. Why should I not do this?
- humanrebar 11y agoWhen you do that, reading your code is like reading Dune for the first time. I have to keep jumping back and forth to the reference to figure out what's going on.
- Grishnakh 11y agoBecause it wastes memory and decreases performance, that's why. If there's a section of code in your function that may or may not be executed, depending on a conditional, then allocating the memory for variables used within that code block will be a waste if you end up not executing it. Now, you could argue that that block should be turned into a separate function, but now you're doing another function call (unless it gets optimized out) which wastes performance, and you're making the code more cluttered if the code block is relatively small and not really worth turning into a separate function.
- majewsky 11y ago> allocating the memory for variables Allocation of N variables on the stack is O(1) since it just moves the stack pointer by the total size of all variables.
- annathebannana 11y agoTerrible The first rule of C is to ALWAYS use C The second rule of C is to NEVER use [u]intN_t, use [u]int_leastN_t instead and (unsigned) char when you want to deal with bytes The 3rd rule of C is to never use anything else when size_t should be used The 4th rule of C is that everyone has his own style, so declaring variables on top is totally acceptable (and more clean IMO) The 5th rule of C is "enjoy your stack overflow" when using VLAs instead of malloc (not to mention that VLAs are optional in C11) >which also means it's capable of holding the largest memory offset in your program. wrong >C99 gives us the power of <stdbool.h> which defines true to 1 and false to 0 I do use it but it is useless >readability-braces-around-statements — force all if/while/for statement bodies to be enclosed in braces don't use a shitty editor >You should always use calloc good thing calloc has: checks for overflows when multiplying the typesize and the arraylen bad thing: makes programmers think that it will make your pointers null >you can wrap it with #define mycalloc(N) calloc(1, N). but the two arguments is the only good thing calloc has >growthOptional I can't see any reason why the second one is considered better
- jimjag 11y ago++1
- tptacek 11y agoIs there any moral difference between using VLAs and calling alloca? Because alloca is generally recognized as evil.
- kps 11y agoThere is no way to recover from a failed VLA allocation. Also, VLAs are optional in C11, and may not be supported in otherwise-near-C99 compilers for small processors. (Of course, alloca() is not standard at all.)
- _kst_ 11y ago> There is no way to recover from a failed VLA allocation. There's no way to recover from any stack allocation. #define TOO_BIG 1000000 This: { double big_array[TOO_BIG]; // not a VLA } is no safer than this: { size_t size = TOO_BIG; // size is not a constant double big_vla[size]; } If you use VLAs (variable length arrays) in a manner that permits arbitrary unchecked sizes, then yes, they're dangerous. If you carefully check the size so that a VLA is no bigger than a corresponding fixed-size array would have been, they don't introduce any new risk. (I'm not sure why VLAs were made optional in C11, at least for hosted implementations. Support for them was mandatory in C99, and I'm skeptical that they impose any significant burden on compilers.)
- julian-klode 11y agoMicrosoft did not implement them in Visual C and wanted to claim C11 conformity without doing the work, so they bullied it out.
- cygx 11y agoIt's a bit less evil because it's a declaration instead of pretending to be a function, but a lot of the criticism works for either. Note that the overarching theme of C99 was making C a better language for numerical code (ie competing with Fortran), thus the introduction of VLAs (as well as _Complex, tgmath.h, restrict, and possibly other things I forgot ).
- Xophmeister 11y agoI thought `ssize_t` was only signed `size_t` in practice. Technically, it only needs to be `size_t` with -1.
- whyever 11y agoHow would that work? You need an additional bit.
- julian-klode 11y agoYou could just store using offsets instead. You don't have to do sign bit. 2 bit example: 00 => -1 01 => 0 10 => 1 11 => 2 or: 2 bit example: 00 => -2 01 => -1 10 => 0 11 => 1 This even wrapsaround naturally.
- jhallenworld 11y ago>If you find yourself typing char [..] into new code, you're doing it wrong. >The only acceptable use of char in 2016 is if a pre-existing API requires char (e.g. strncat, printf'ing "%s", ...) or if you're initializing a read-only string (e.g. const char hello = "hello";) because the C type of string literals ("hello") is char . Heh, basically you need to use 'char ' for strings. I would far prefer if strings were unsigned so that I could use 'uint8_t ', but if you try it you'll get tons of conversion warnings and your code will look weird.
- bluetomcat 11y agoThe other widespread use of char pointers is when you need to perform pointer arithmetic over a buffer (without any dereferencing) in steps of 1 (as sizeof(char) == 1). Void pointers do not allow pointer arithmetic (GCC allows it as an extension).
- gvb 11y agoHe covers that in the next section "Signedness" and more thoroughly in "System-Dependent Types" where he recommands (properly) using uintptr_t and intptr_t: The correct type for pointer math is uintptr_t defined in <stddef.h>.
- bluetomcat 11y agoCorrect me if I'm wrong, but in my understanding, the primary purpose of uintptr_t is to type-pun a pointer into a compatible integer, inspect/modify the bits of the integer and then cast it back to a pointer of the same type. I don't feel like it's the right type for simple pointer arithmetic.
- _kst_ 11y agoYou're right, using uintptr_t for pointer arithmetic is a bad idea. I've worked on systems (Cray vector machines) where converting a pointer to an integer type, performing arithmetic on the integer, and converting back to a pointer could yield meaningless results. (Hardware addresses were 64-bit addresses of 64-bit words; byte pointers were implemented by storing a 3-bit offset in the high-order bits of the pointer.) Pointer arithmetic works. It causes undefined behavior if it goes outside the address range of the enclosing object, but in those cases converting to uintptr_t and performing arithmetic will give you garbage results anyway.
- asveikau 11y agoNever use int? Use uint8_t instead of char for strings? These are not best practices, this is a lot of terrible advice that will make many c people laugh at you.
- forrestthewoods 11y ago"The first rule of C is don't write C if you can avoid it." Eek! Not what I was expecting. In the video game world there is a not so small contingent of "write everything in C unless you have a really damn good reason not to". Great post though. Now go forth into the world and write C, my friends!
- jzwinck 11y agoThe clang-format wrapper script presented has a bug. The $@ at the end must be quoted for it to work on filenames containing spaces. For bonus points it shouldd prefix the clang-format command with "exec" but that's not a correctness issue.
- minitech 11y agoDo you mean “must not be quoted”? $@ shouldn’t be, most of the time.
- jzwinck 11y agoNo typo. It must be quoted as "$@". If you think otherwise, test with some filenames containing spaces. Or read here: http://stackoverflow.com/questions/448407/bash-script-to-receive-and-repass-quoted-parameters http://stackoverflow.com/questions/448407/bash-script-to-rec...
- cptaffe 11y ago> #define mycalloc(N) calloc(1, N) I am not a fan of the use of macros here. You should never use macros. Instead maybe: static inline void *mycalloc(size_t sz) { return calloc(1, sz); }
- kazinator 11y ago> Standard C99 No thanks; sticking to C90. > LTO Violates ISO C, which says that semantic analysis is done in translation phase 7, and only linking takes place translation phase 8.
- izacus 11y agoYou could at least add some information about "why" for both of those :/
- kazinator 11y agoThe "why" for the second one is: that's the model of the C language. Programs are translated in units. The semantic analysis ends when a translation unit is analyzed. All that is left is to resolve the external references when multiple units are linked; nothing is re-translated. This is very clearly stated in a few sentences in that section of the standard which describes the translation phases one by one. Link-time optimization is a language extension which alters the concept of a translation unit. In some ways, multiple translation units become one unit (such as: they inline each other's code, or code in one is altered based on the behavior of code in the other). But in other ways they aren't one translation unit (e.g. their file scope static names are still mutually invisible.) One reason I don't use C99 is that many of its features aren't C++ compatible. Most of C90 is. With just a little effort, a C90 program compiles as C++ and behaves the same way, and no significant features of C90 have to be wholly excluded from use. Portability to C++ increases the exposure of the program to more compilers, with better safety and better diagnostics. In some cases, I can use constructs that are specially targetted to C or C++ via macros. Instead of a C cast, I can have convert(type, val) macro which uses the C++ static_cast when compiled as C++, a coerce(type, val) which uses reinterpret_cast, or strip_qual(type, val) which uses const_cast. I get the diagnostics under C++, but the code still targets C. I'm also prevented from converting from void * without a cast, and there are other benefits like detecting duplicate external object names. This way of programming in C and C++ was dubbed "Clean C" by the Harbison and Steele reference manual. C90 is a cleaner, smaller language than C99 without extraneous "garbage features" like variable-length arrays that allocate arbitrary amounts of storage on the stack without any way to indicate failure to do so. The useful library extensions in C99 like snprintf or newer math functions can be used from C90; you can detect them in a configure script like any library. The overall main reason is that I simply don't have faith in the ISO C process. The last good standard that was put out was C90. After that, I don't agree with the direction it has been taking. Not all of it is bad, but we should go back to C90 and start a better fork, with better people. git checkout -b c90-fork c90 git cherry-pick ... what is good from c99-branch
- deleted 11y ago[deleted]
- deleted 11y ago[deleted]
- deleted 11y ago[deleted]
- stephen_g 11y agoWhy? I write a whole lot of pure C, on microcontrollers (usually with an RTOS) and really cut-down embedded Linux systems that don't have a C++ library. Why wouldn't you use classes if you have access to C++ though? A lot of the time I write C, I spend a lot of time and write a lot of code that would be far easier with classes and/or access to the STL.
- SFjulie1 11y agoAdvice forgotten: - you should always avoid library whose API documentation are unaccessible or poorly written; - unix man page is a great format but windows has good help format too (synopsis, signature, use case, error handling, meaning of constants, caveats), - always check every function you are using from time to time ... human memory is not perfect, even open/calloc ....; - don't copy paste example code blindly from man or stackoverflow ... (I remember windows C++ doc having horrible example with bugs); Have a reference to the norm easily accessible and don't use options of the compiler which impact you don't fully know. Most developer are poorly understanding multithreading and it can be tricky to make a portable library that has this property. Don't hesitate to stipulate in your documentation that you did not cared about it, people can then use languages with GIL (ruby, python ...) to safely overcome this issue. Modern language are social, take advantage of sociability (nanomsg, swift, extension, #include "whatever_interpreter.h" ....). Programming in C with dynamic structures is guaranteed to be like a blind man walking in a mine field: you totally have the right either to make C extension to modern language OR include stuff like python.h and use python data structure managed by the garbage collector from C. Old style pre allocated arrays may not be elegant, but that's how critical system avoid a lot of problems. DO NOT USE MAGIC NUMBERS... USE GOTO for resource cleaning on error and make the label of the goto indentend at EXACTLY the same level the goto was written, especially when you have a resources that are coupled (ex: a file and a socket). Coupling should be avoid, but sometimes it cannot. Remember Djikstra is not that smart, he was unable to be a fully pledged physicist that can deal with the real world. Avoiding the use of global variable is nice, but some are required, don't fall for the academic bias of hiding them to make your code look like it has none. Copy pasting function is not always stupid (especially if you use only 1 clearly understandable function out of a huge library). Be critical: some POSIX abstraction like threads seems to be broken by complexity: KISS. Modern C compiler are less and less deterministic, learn about C undefined behaviour and avoid clang or GNU specific optimisations and CHECK your code gives the same results with at least 2 compilers. Don't follow advices blindly: there are some C capos out there that dream an ideal world. Code with your past errors in mind and rely on your nightmarish past experiences to guide you to avoid traps. Feeling miserable and incompetent is some time good. Resources are limited : ALWAYS check the results of resource exhaustion in C instead of relying on systems and always have error messages preallocated and preferably don't i18n them. Don't trust the OS to do stuff for you (closing files, SIGSEV/SEGFAULT ...). KISS KISS KISS complexity is your enemy in C much more than in other languages. Modern C/C++ want you to build complex architecture on libraries that are often bloated (glibc). Egyptians are nice looking. Once you finished coding you have done only the easy part in C: - dependency management is hard in C; - have a simple deterministic build chain; - you should write your man pages too; - you should aim at writing portable code; - static code analysis, maybe fuzzing, getting rid of all warnings is the last important part. Coding in C is just about 15% of the time required to make great software. You will probably end up debugging it much more than you write it: write code for maintainability as your first priority and do not under any circumstances think that you can handle multiple priorities (memory use, speed, ...). Above all : just write maintainable code, please. This rule is the golden rule of C and it has not changed.
- peter303 11y ago2016 is my 40th year of using C. I prefer Java.
- ape4 11y ago> It's important to not remain stuck in your "things I learned in the 80s/90s" mindset of C development. Instead use C99 ... which is from 1999. (Yes, I know most people didn't learn it in the 90s)
- viraptor 11y agoThat's a bit unfair. The standard is from '99, but the implementation happened differently than c11 (which already had many bits implemented in clang before it was released). In practice a lot of c99 got into gcc after 3.0 (2001) and it still took a bit of time for any distribution to include it as stable. If I remember correctly, lots of distributions held on to 2.9x for as long as possible. I'd say realistic c99 support has ~10 years now, rather than 17.
- vitaut 11y agoIt's a bit sad that the formatting API hasn't improved in C over the years. Take this code, for example: printf("Local number: %" PRIdPTR "\n\n", someIntPtr); which is quite unreadable to say the least. To address this issue I even wrote a small library (https://github.com/cppformat/cppformat https://github.com/cppformat/cppformat) that allows you to write something like this instead: print("Local number: {}\n\n", someIntPtr); It is written in C++ rather than C, because the latter doesn't have facilities (at least overloading is necessary) to implement such library.
- jhallenworld 11y agoThis is quite a nice library, I'll try it. Enhancement requests: - Option to insert commas or underscores every three digits (for decimal) or four digits (hex/binary/octal). - Option to print floating in engineering format (make exponent a multiple of 3). - Option to print floating but with a specific given exponent.
- vitaut 11y agoThanks for the suggestions. There has been some work to make formatting more customizable (https://github.com/cppformat/cppformat/issues/235 https://github.com/cppformat/cppformat/issues/235). This will enable extensions like the ones you suggest while keeping the library itself lean. Generally useful extensions like thousand separators can be added to the core library of course. They are actually on my TODO list.
- nly 11y agoThanks btw. I use your library and love it.
- nwmcsween 11y ago_Generic?
- Joky 11y agoC (or C++) in 2016 and no mention of ASAN/UBSAN(/TSAN)? This seems like a must nowadays.
- wyldfire 11y ago> For modern programs, you should #include <stdint.h> then use standard types. Unless you're doing memory-mapped IO, or other very-specific things, you probably only need the _least_t or _fast_t types (int16_least_t, e.g.).
- shusx1234 11y agoa rubbishy article
- shusx1234 11y agoa rubbishy article
- justncase80 11y agoFor an example of a high quality modern C api I highly recommend taking a look at libuv: http://libuv.org/ http://libuv.org/
- ChuckMcM 11y agoSince I like coding in C, I am mildly offended by the notion that you should not write code in it, but I really enjoyed that all of these things were put together in one place.
- slmyers 11y agoFantastic article. This is exactly how the language should be taught in university. At least I wish I was taught to code like this in school.
- saurabhtandon 11y agoI learned and used C at school. It appreciate what I learned but using it in the right way, using the latest functionality available, considering machine portability along with safe coding guidelines of code would have been better.
- agentultra 11y agoNice list! I like using anonymous structs with variable initializers to provide parameters to functions with default values -- nice and clean! C99 is not your grandparents' C. It has kept up with the times. A more thorough resource I cannot recommend highly enough: 21st Century C[0] [0] http://shop.oreilly.com/product/0636920033677.do http://shop.oreilly.com/product/0636920033677.do
- cpeterso 11y agoclang has a -Weverything flag that, unlike "-Wall", will enable all compiler warnings. This includes possibly contradictory warnings. :) For new C code, -Weverything is a good place to start. Unlike gcc, clang does not have good documentation for its warning options. The best sources I've found are the clang code itself (https://github.com/llvm-mirror/clang/blob/master/include/clang/Basic/DiagnosticGroups.td https://github.com/llvm-mirror/clang/blob/master/include/cla...) and http://fuckingclangwarnings.com/ http://fuckingclangwarnings.com/.
- cpeterso 11y agoThis article focuses on clang and gcc, but note that MSVC's printf functions do not support %zu (or %zd) for size_t. MSVC uses %Iu. There is no standard "PRIuSIZE" macro for size_t, but Mozilla defines a polyfill for Firefox: https://hg.mozilla.org/mozilla-central/file/tip/mfbt/SizePrintfMacros.h https://hg.mozilla.org/mozilla-central/file/tip/mfbt/SizePri...
- dllthomas 11y agoMy biggest tip for writing maintainable C for modern compilers is to avoid passing bare primitives between functions. Don't just typedef - wrap things in (single-element) structs that describe the contents. The compilers these days understand that they can boil off the wrapper when generating code.
- deleted 11y ago[deleted]
- btrask 11y agoI recently wrote a similar article ("C Programming Substance Guide")[1] which made the rounds here[2]. I've updated and improved it quite a bit since then too. It seems like we agree on the major points but it might be a good second opinion. [1] https://github.com/btrask/stronglink/blob/master/SUBSTANCE.md https://github.com/btrask/stronglink/blob/master/SUBSTANCE.m... [2] https://news.ycombinator.com/item?id=10157018 https://news.ycombinator.com/item?id=10157018
- synack 11y agoWould love to see a discussion of build systems for modern code. Trying to decide between autoconf/cmake/scons/ninja/gyp for new projects is like flipping a coin... They all have their advantages and disadvantages and there is no clear winner.
- admax88q 11y ago"How to C in 2016" Don't.
- admax88q 11y ago"How to C in 2016" Don't. C has outlived its usefulness. It's time to move on to more expressive languages. The fact that buffer overflows are still a thing in 2016 should be an embarrassment to the industry.
- na85 11y agoNot everyone can run node or V8 on their embedded microcontroller.
- ThatGeoGuy 11y agoPerhaps, but Rust and Chicken Scheme are probably applicable in many of these cases (embedded microcontroller). Few people in the field give either of these any appreciation, despite being viable. I will agree that "Don't use C ever" is maybe a poor attitude to take as there are definitely projects that need C as a hard requirement; however, I think your snark is somewhat unjustified as we do have languages that compile to C or compile with no runtime that can beat C in some of these areas that traditionally have been dominated by C.
- Gibbon1 11y agoPersonal opinion, people are generally way too concerned about buffer overflows in C. Meaning if you have issues with buffer overflows the root cause is you are seriously writing C the wrong way circa 2016. When I hear buffer overflows I think. Programmer trying to be smarter than he is. Depreciated unsafe string functions. Unnecessary pointer arithmetic. No unit tests Failing to use static code analysis tools
- admax88q 11y ago> Personal opinion, people are generally way too concerned about buffer overflows in C My only concern about buffer overflows in C is that memory error continue to happen in software that I depend upon in my daily life. Chrome, Firefox, Linux, OpenSSL, all these things suffer memory errors that compromise my security. Anyone doing security work in C in 2016 is in my opinion committing malpractice and putting user's at risk because their ego's can't take not fiddling bits by hand.
- tomcam 11y agoFor me, this is the most important bookmark of the year. Dropping back into C after using pre C11 it is tailor made for me. Covers all the areas I needed: Correct non-K&R datatypes, new formatting options, best practices on arrays. Awesome.
- aback 11y ago> There's not much room for "casual observer C development." For the rest of the world, that's why we have Erlang. lmaoed at that one
- jrcii 11y agoLearning C is intimidating to me because every time I see a post written by a long-time C programmer, there are 10 more long-time C programmers explaining how the post is incorrect. Most C books on Amazon have reviews wherein credible-sounding programmers criticize the mistakes of the author. If publishers and programmers with popular blogs can't get a consensus on what constitutes correct C, how could I ever hope to produce it? Case in point https://news.ycombinator.com/item?id=10864601 https://news.ycombinator.com/item?id=10864601
- zonovar 11y agoDon't care about those people. Those are just the one that in the real world never finish a single project because they jerk off too much on the code. Just focus about getting some code working, as long as you're learning you've always room for improvements. You'll make mistakes and you'll get better.
- greydius 11y agoThis is a valid observation. Setting aside all the technical flaws with the C language, the fact that most supposed experts can't agree on best practices for even the most trivial usage details does not inspire (for me, at least) much confidence that it would be a good language choice.
- muricula 11y agoIs it better to fully qualify structs and unions or to hide their names by typedefs? I frequently see people hide all struct types because it's visual clutter and extra typing. In other words, given some struct a, should I do: typedef struct a a_t; I realize this is a bit of a style question, but I'm curious.
- rcfox 11y agoWhichever way you go, you should not append _t to the end of the name. That's reserved by POSIX. http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_02_02 http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2...
- haldean 11y agoOne trick I've been using a lot recently that saves me from forgetting to check error codes is the judicious use of __attribute__((__warn_unused_result__)) on functions that return an error code. It prevents you from being able to ignore the return value without doing something with it; even just assigning it to a variable and never checking the value in the variable isn't enough to silence the warning.
- typon 11y agoC Programmers bragging about writing good C in this thread is just funny. A large majority of new code does NOT need to be written in C. There's nothing extra-ordinary about knowing how to write C. Learn to write HDL if you want to brag.
- Kenji 11y agoWow, I wish I knew all of this when I started a big project last year... It's kinda hard to change it NOW. And with that I mean it would be endless rewriting of the entire thing.
- deevus 11y agoI wrote an assignment in C for Operating Systems in my second year of CS. I did it mostly for the challenge and ended up with a distinction grade. It was very challenging! I was learning how to do threading in C for what was one of the most complex assignments in my degree. However, I don't regret a moment of it. Even with a mark that was lower than my average programming assignment marks, I learned more in that assignment than I did in anything else while at uni. Being forced to RTFM because it wasn't abstracted away for you was exhilarating once I understood what was going on. And man, it was fast! Much faster than others who had programmed in Java. I wish I had more reasons to write C, because I love it.
- camgunz 11y agoI refuse to believe that the wicked Buffer Overflow has completely stymied humanity. Use array/string/buffer libraries. They're very small and very easy to test. Pass them around instead of a "char *" and a "size_t". Now, if you want to talk about signed overflow, bitwise operations on signed types, wacky undefined behavior, or memory management, that stuff is pretty hard to deal with in C. But 99% of the time when people are shaking their fists at "shitty ol' C ruining the Internet for everyone", they mean buffer overflows, and it just shouldn't be an issue these days.
- egovtjobs 11y agoAwesome Information in the post http://egovtjobs.in/ http://egovtjobs.in/
- tilt_error 11y agoHaving worked with a large code base, maintained over a period of time (25+) by multiple persons has learned me respect and that even old code have a value in virtue of being running without problems over time. A piece of software that is running without problems has a value in itself that have to be taken into account when considering the cost of maintenance. Respect earlier developers and try to minimise _unnecessary_ changes that break version control history. Did earlier developers use names you don't like? Formatting you don't like? InconsistentCamelCasing or other_naming_paradigms you don't like? Let it be -- at least until you do a refactoring where _you_ claim responsibility. It may not be a good point to uncritically and retroactively apply the good points from the original article in an old code base.
- hyc_symas 11y agoThe biggest failure of this article is the multiple admonishments "never do <blah> (except for this, this, and that other case over there" - all advice of this form is worthless when your supposed thesis is making code readable and consistent. Consistency means avoiding special cases. Variable length arrays are too prone to special cases - IMO, just don't use them. malloc vs calloc is too prone to special cases, that rule is worthless. The rule "never use memset" is full of special cases, also worthless. (And in fact, for your larger automatic initializations, gcc just generates code to call memset anyway.) I would say "always use memset when you need zeroing - then you'll never be surprised." The fewer special cases you have to keep track of, the better. "Never cast arguments to printf - oh but for %p, always cast your pointers to (void *)" again, worthless advice with an unacknowledged special case. The way to write code that always works right is to always use consistent methodology and avoid any mechanism that is rife with special cases. The advice "always return 'true' or 'false' instead of numeric result codes" is worthless. Functions should always return explicit numeric result codes with explicitly spelled out meanings. enum is nice for this purpose because the code name is preserved in debug builds, saves you time when working inside a debugger. But most of all, it's a waste of time for a function to return a generic false/fail value which then requires you to take some other action to discover the specific failure reason. POSIX errno and Windows GetLastError() are both examples of stupid API design. (Of course, even this rule has an exception - if the function cannot fail, then just make it return void.)