· 9 years ago · Oct 27, 2016, 01:20 PM
1How to C in 2016
2
3This is a draft I wrote in early 2015 and never got around to publishing. Here's the mostly unpolished version because it wasn't doing anybody any good sitting in my drafts folder. The simplest change was updating year 2015 to 2016 at publication time.
4
5(Update: Many people have submitted revisions, notes, and improvements. All contributions have been incorporated throughout the page below.)
6
7Feel free to submit fixes/improvements/complaints as necessary. -Matt
8
9Adrián Arroyo Calle provides a Spanish translation at ¿Cómo programar en C (en 2016)?
10
11Japanese POSTD provides a Japanese translation at 2016å¹´ã€C言語ã¯ã©ã†æ›¸ãã¹ãã‹ (å‰ç·¨) and 2016å¹´ã€C言語ã¯ã©ã†æ›¸ãã¹ãã‹ (後編).
12
13Chinese InfoQ provides a Chinese translation at Cè¯è¨€çš„2016.
14
15Programmer Magazine provides a Chinese translation at 2016年,Cè¯è¨€è¯¥æ€Žæ ·å†™ (as PDF too).
16
17Keith Thompson provides a nice set of corrections and alternative opinions at howto-c-response.
18
19Rob Graham provides a response covering other avenues out of scope here at Some notes C in 2016.
20
21Nick Galbreath collected some relevant links about overall C programming as well.
22
23Now on to the article...
24
25The first rule of C is don't write C if you can avoid it.
26
27If you must write in C, you should follow modern rules.
28
29C has been around since the early 1970s. People have "learned C" at various points during its evolution, but knowledge usually get stuck after learning, so everybody has a different set of things they believe about C based on the year(s) they first started learning.
30
31It's important to not remain stuck in your "things I learned in the 80s/90s" mindset of C development.
32
33This page assumes you are on a modern platform conforming to modern standards and you have no excessive legacy compatibility requirements. We shouldn't be globally tied to ancient standards just because some companies refuse to upgrade 20 year old systems.
34Preflight
35
36Standard c99 (c99 means "C Standard from 1999"; c11 means "C Standard from 2011", so 11 > 99).
37
38 clang, default
39 clang uses an extended version of C11 by default (GNU C11 mode), so no extra options are needed for modern features.
40 If you want standard C11, you need to specify -std=c11; if you want standard C99, use -std=c99.
41 clang compiles your source files faster than gcc
42 gcc requires you specify -std=c99 or -std=c11
43 gcc builds source files slower than clang, but sometimes generates faster code. Performance comparisons and regression testings are important.
44 gcc-5 defaults to GNU C11 mode (same as clang), but if you need exactly c11 or c99, you should still specify -std=c11 or -std=c99.
45
46Optimizations
47
48 -O2, -O3
49 generally you want -O2, but sometimes you want -O3. Test under both levels (and across compilers) then keep the best performing binaries.
50 -Os
51 -Os helps if your concern is cache efficiency (which it should be)
52
53Warnings
54
55 -Wall -Wextra -pedantic
56 newer compiler versions have -Wpedantic, but they still accept the ancient -pedantic as well for wider backwards compatibility.
57 during testing you should add -Werror and -Wshadow on all your platforms
58 it can be tricky deploying production source using -Werror because different platforms and compilers and libraries can emit different warnings. You probably don't want to kill a user's entire build just because their version of GCC on a platform you've never seen complains in new and wonderous ways.
59 extra fancy options include -Wstrict-overflow -fno-strict-aliasing
60 Either specify -fno-strict-aliasing or be sure to only access objects as the type they have at creation. Since so much existing C code aliases across types, using -fno-strict-aliasing is a much safer bet if you don't control the entire underlying source tree.
61 as of now, Clang reports some valid syntax as a warning, so you should add -Wno-missing-field-initializers
62 GCC fixed this unnecessary warning after GCC 4.7.0
63
64Building
65
66 Compilation units
67 The most common way of building C projects is to decompose every source file into an object file then link all the objects together at the end. This procedure works great for incremental development, but it is suboptimal for performance and optimization. Your compiler can't detect potential optimizations across file boundaries this way.
68 LTO — Link Time Optimization
69 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.
70 LTO can slow down the linking process noticeably, but make -j helps if your build includes multiple non-interdependent final targets (.a, .so, .dylib, testing executables, application executables, etc).
71 clang LTO (guide)
72 gcc LTO
73 As of 2016, clang and gcc releases support LTO by just adding -flto to your command line options during object compilation and final library/program linking.
74 LTO still needs some babysitting though. Sometimes, if your program has code not used directly but used by additional libraries, LTO can evict functions or code because it detects, globally when linking, some code is unused/unreachable and doesn't need to be included in the final linked result.
75
76Arch
77
78 -march=native
79 give the compiler permission to use your CPU's full feature set
80 again, performance testing and regression testing is important (then comparing the results across multiple compilers and/or compiler versions) is important to make sure any enabled optimizations don't have adverse side effects.
81 -msse2 and -msse4.2 may be useful if you need to target not-your-build-machine features.
82
83Writing code
84Types
85
86If you find yourself typing char or int or short or long or unsigned into new code, you're doing it wrong.
87
88For modern programs, you should #include <stdint.h> then use standard types.
89
90For more details, see the stdint.h specification.
91
92The common standard types are:
93
94 int8_t, int16_t, int32_t, int64_t — signed integers
95 uint8_t, uint16_t, uint32_t, uint64_t — unsigned integers
96 float — standard 32-bit floating point
97 double - standard 64-bit floating point
98
99Notice we don't have char anymore. char is actually misnamed and misused in C.
100
101Developers routinely abuse char to mean "byte" even when they are doing unsigned byte manipulations. It's much cleaner to use uint8_t to mean single a unsigned-byte/octet-value and uint8_t * to mean sequence-of-unsigned-byte/octet-values.
102Special Standard Types
103
104In addition to standard fixed-width like uint16_t and int32_t, we also have fast and least types defined in the stdint.h specification.
105
106Fast types are:
107
108 int_fast8_t, int_fast16_t, int_fast32_t, int_fast64_t — signed integers
109 uint_fast8_t, uint_fast16_t, uint_fast32_t, uint_fast64_t — unsigned integers
110
111Fast types provide a minimum of X bits, but there is no guarantee the underlying storage size is exactly what you request. If a larger type has better support on your target platform, a fast type will automatically use the better supported larger type.
112
113The best example here is, on some 64-bit systems, when you request uint_fast16_t you actually get a uint64_t because operating on word-sized integers will be faster than operating on half of a 32-bit integer.
114
115The fast guidelines aren't followed on every system though. One standout is OS X, where fast types are defined exactly as their corresponding fixed width counterparts.
116
117Fast types can be useful for self-documenting code as well. If you know your counters only need 16 bits, but you prefer your math use 64 bit integers because they are faster on your platform, that's where uint_fast16_t would help. Under 64-bit Linux platforms, uint_fast16_t gives you a fast 64-bit counter while maintaining the code-level inline documentation of "we only need 16 bits here."
118
119One thing to be aware of for fast types: it can impact certain test cases. If you need to test for storage width edge cases, having uint_fast16_t be 16 bits on some platforms (OS X) and 64 bits on other platforms (Linux) can increase the minimum number of platforms where your tests need to pass.
120
121Fast types do introduce the same uncertainty as int not being a standard size across platforms, but with fast types, you can limit your uncertainty to known-safe locations in your code (counters, temporary values with checked bounds, etc).
122
123Least types are:
124
125 int_least8_t, int_least16_t, int_least32_t, int_least64_t — signed integers
126 uint_least8_t, uint_least16_t, uint_least32_t, uint_least64_t — unsigned integers
127
128Least types provide you with the most compact number of bits for the type you request.
129
130The least guidelines, in practice, mean least types are just defined to standard fixed width types, since standard fixed width types already provide the exact minimum number of bits you request.
131to int or not to int
132
133Some readers have pointed out they truly love int and you'll have to pry it from their cold dead fingers. I'd like to point out is is technically impossible to program correctly if the sizes of your types change out from under you.
134
135Also see RATIONALE included with inttypes.h for reasons why using non-fixed-width types is unsafe. If you are truly smart enough to conceptualize int being 16 bits on some platforms and 32 bits on other platforms throughout your development while also testing all 16 bit and 32 bit edge cases for every place you use int, please feel free to use int.
136
137For the rest of us who can't hold entire multi-level decision tree platform specification hierarchies in our heads while writing fizzbuzz, we can use fixed width types and automatically have more correct code with much less conceptual hassle and much less required testing overhead.
138
139Or, said more concisely in the specification: "the ISO C standard integer promotion rule can produce silent changes unexpectedly."
140
141Good luck with that.
142One Exception to never-char
143
144The 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 [].
145
146ALSO: In C11 we have native unicode support, and the type of UTF-8 string literals is still char [] even for multibyte sequences like const char *abcgrr = u8"abc😬";.
147One Exception to never-{int,long,etc}
148
149If you are using a function with native return types or native parameters, use types as described by the function prototype or API specification.
150Signedness
151
152At no point should you be typing the word unsigned into your code. We can now write code without the ugly C convention of multi-word types that impair readability as well as usage. Who wants to type unsigned long long int when you can type uint64_t? The <stdint.h> types are more explicit, more exact in meaning, convey intentions better, and are more compact for typographic usage and readability.
153Pointers as Integers
154
155But, you may say, "I need to cast pointers to long for dirty pointer math!"
156
157You may say that. But you are wrong.
158
159The correct type for pointer math is uintptr_t defined by <stdint.h>, while the also useful ptrdiff_t is defined by stddef.h.
160
161Instead of:
162
163long diff = (long)ptrOld - (long)ptrNew;
164
165Use:
166
167ptrdiff_t diff = (uintptr_t)ptrOld - (uintptr_t)ptrNew;
168
169Also:
170
171printf("%p is unaligned by %" PRIuPTR " bytes.\n", (void *)p, ((uintptr_t)somePtr & (sizeof(void *) - 1)));
172
173System-Dependent Types
174
175You continue arguing, "on a 32 bit platform I want 32 bit longs and on a 64 bit platform I want 64 bit longs!"
176
177If we skip over the line of thinking where you are deliberately introducing difficult to reason about code by using two different sizes depending on platform, you still don't want to use long for system-dependent types.
178
179In these situations, you should use intptr_t — the integer type capable of holding a pointer value for your platform.
180
181On modern 32-bit platforms, intptr_t is int32_t.
182
183On modern 64-bit platforms, intptr_t is int64_t.
184
185intptr_t also comes in a uintptr_t flavor.
186
187For holding pointer offsets, we have the aptly named ptrdiff_t which is the proper type for storing values of subtracted pointers.
188Maximum Value Holders
189
190Do you need an integer type capable of holding any integer usable on your system?
191
192People tend to use the largest known type in this case, such as casting smaller unsigned types to uint64_t, but there's a more technically correct way to guarantee any value can hold any other value.
193
194The safest container for any integer is intmax_t (also uintmax_t). You can assign or cast any signed integer to intmax_t with no loss of precision, and you can assign or cast any unsigned integer to uintmax_t with no loss of precision.
195That Other Type
196
197The most widely used system-dependent type is size_t and is provided by stddef.h.
198
199size_t is basically as "an integer capable of holding the largest array index" which also means it's capable of holding the largest memory offset in your program.
200
201In practical use, size_t is the return type of sizeof operator.
202
203In either case: size_t is practically defined to be the same as uintptr_t on all modern platforms, so on a 32-bit platform size_t is uint32_t and on a 64-bit platform size_t is uint64_t.
204
205There is also ssize_t which is a signed size_t used as the return value from library functions that return -1 on error. (Note: ssize_t is POSIX and does not apply to Windows interfaces.)
206
207So, should you use size_t for arbitrary system-dependent sizes in your own function parameters? Technically, size_t is the return type of sizeof, so any functions accepting a size value representing a number of bytes is allowed to be a size_t.
208
209Other uses include: size_t is the type of the argument to malloc, and ssize_t is the return type of read() and write() (except on Windows where ssize_t doesn't exist and the return values are just int).
210Printing Types
211
212You should never cast types during printing.
213
214Always use proper type specifiers as defined by inttypes.h.
215
216These include, but are not limited to:
217
218 size_t - %zu
219 ssize_t - %zd
220 ptrdiff_t - %td
221 raw pointer value - %p (prints hex in modern compilers; cast your pointer to (void *) first)
222 int64_t - "%" PRId64
223 uint64_t - "%" PRIu64
224 64-bit types should only be printed using PRI[udixXo]64 style macros.
225 Why?
226 on some platforms a 64-bit value is a long and on others it's a long long. These macros provide the proper underlying format specification across platforms.
227 it is actually impossible to specify a correct cross-platform format string without these format macros because the types change out from under you (and remember, casting values before printing is not safe or logical).
228 intptr_t — "%" PRIdPTR
229 uintptr_t — "%" PRIuPTR
230 intmax_t — "%" PRIdMAX
231 uintmax_t — "%" PRIuMAX
232
233One note about the PRI* formatting specifiers: they are macros and the macros expand to proper printf type specifiers on a platform-specific basis. This means you can't do:
234
235printf("Local number: %PRIdPTR\n\n", someIntPtr);
236
237but instead, because they are macros, you do:
238
239printf("Local number: %" PRIdPTR "\n\n", someIntPtr);
240
241Notice you put the % inside your format string literal, but the type specifier is outside your format string literal because all adjacent strings get concatentated by the preprocessor into one final combined string literal.
242C99 allows variable declarations anywhere
243
244So, do NOT do this:
245
246void test(uint8_t input) {
247 uint32_t b;
248
249 if (input > 3) {
250 return;
251 }
252
253 b = input;
254}
255
256do THIS instead:
257
258void test(uint8_t input) {
259 if (input > 3) {
260 return;
261 }
262
263 uint32_t b = input;
264}
265
266Caveat: if you have tight loops, test the placement of your initializers. Sometimes scattered declarations can cause unexpected slowdowns. For regular non-fast-path code (which is most of everything in the world), it's best to be as clear as possible, and defining types next to your initializations is a big readability improvement.
267C99 allows for loops to declare counters inline
268
269So, do NOT do this:
270
271 uint32_t i;
272
273 for (i = 0; i < 10; i++)
274
275Do THIS instead:
276
277 for (uint32_t i = 0; i < 10; i++)
278
279One exception: if you need to retain your counter value after the loop exits, obviously don't declare your counter scoped to the loop itself.
280Modern compilers support #pragma once
281
282So, do NOT do this:
283
284#ifndef PROJECT_HEADERNAME
285#define PROJECT_HEADERNAME
286.
287.
288.
289#endif /* PROJECT_HEADERNAME */
290
291Do THIS instead:
292
293#pragma once
294
295#pragma once tells the compiler to only include your header once and you do not need three lines of header guards anymore. This pragma is widely supported across all compilers across all platforms and is recommended over manually naming header guards.
296
297For more details, see list of supported compilers at pragma once.
298C allows static initialization of auto-allocated arrays
299
300So, do NOT do this:
301
302 uint32_t numbers[64];
303 memset(numbers, 0, sizeof(numbers));
304
305Do THIS instead:
306
307 uint32_t numbers[64] = {0};
308
309C allows static initialization of auto-allocated structs
310
311So, do NOT do this:
312
313 struct thing {
314 uint64_t index;
315 uint32_t counter;
316 };
317
318 struct thing localThing;
319
320 void initThing(void) {
321 memset(&localThing, 0, sizeof(localThing));
322 }
323
324Do THIS instead:
325
326 struct thing {
327 uint64_t index;
328 uint32_t counter;
329 };
330
331 struct thing localThing = {0};
332
333IMPORTANT NOTE: If your struct has padding, the {0} method does not zero out extra padding bytes. For example, struct thing has 4 bytes of padding after counter (on a 64-bit platform) because structs are padded to word-sized increments. If you need to zero out an entire struct including unused padding, use memset(&localThing, 0, sizeof(localThing)) because sizeof(localThing) == 16 bytes even though the addressable contents is only 8 + 4 = 12 bytes.
334
335If you need to re-initialize already allocated structs, declare a global zero-struct for later assignment:
336
337 struct thing {
338 uint64_t index;
339 uint32_t counter;
340 };
341
342 static const struct thing localThingNull = {0};
343 .
344 .
345 .
346 struct thing localThing = {.counter = 3};
347 .
348 .
349 .
350 localThing = localThingNull;
351
352If you are lucky enough to be in a C99 (or newer) environment, you can use compound literals instead of keeping a global "zero struct" around (also see, from 2001, The New C: Compound Literals).
353
354Compound literals allow your compiler to automatically create temporary anonymous structs then copy them onto a target value:
355
356 localThing = (struct thing){0};
357
358C99 added variable length arrays (C11 made them optional)
359
360So, do NOT do this (if you know your array is tiny or you are just testing something quickly):
361
362 uintmax_t arrayLength = strtoumax(argv[1], NULL, 10);
363 void *array[];
364
365 array = malloc(sizeof(*array) * arrayLength);
366
367 /* remember to free(array) when you're done using it */
368
369Do THIS instead:
370
371 uintmax_t arrayLength = strtoumax(argv[1], NULL, 10);
372 void *array[arrayLength];
373
374 /* no need to free array */
375
376IMPORTANT CAVEAT: variable length arrays are (usually) stack allocated just like regular arrays. If you wouldn't create a 3 million element regular array statically, don't attempt to create a 3 million element array at runtime using this syntax. These are not scalable python/ruby auto-growing lists. If you specify a runtime array length and the length is too big for your stack, your program will do awful things (crashes, security issues). Variable Length Arrays are convenient for small, single-purpose situations, but should not be relied on at scale in production software. If sometimes you need a 3 element array and other times a 3 million element array, definitely do not use the variable length array capability.
377
378It's good to be aware of the VLA syntax in case you encounter it live (or want it for quick one-off testing), but it can almost be considered a dangerous anti-pattern since you can crash your programs fairly simple by forgetting element size bounds checks or by forgetting you are on a strange target platform with no free stack space.
379
380NOTE: You must be certain arrayLength is a reasonable size in this situation. (i.e. less than a few KB, sometime your stack will max out at 4 KB on weird platforms). You can't stack allocate huge arrays (millions of entries), but if you know you have a limited count, it's much easier to use C99 VLA capabilities rather than manually requesting heap memory from malloc.
381
382DOUBLE NOTE: there is no user input checking above, so the user can easily kill your program by allocating a giant VLA. Some people go as far to call VLAs an anti-pattern, but if you keep your bounds tight, it can be a tiny win in certain situations.
383C99 allows annotating non-overlapping pointer parameters
384
385See the restrict keyword (often __restrict)
386Parameter Types
387
388If a function accepts arbitrary input data and a length to process, don't restrict the type of the parameter.
389
390So, do NOT do this:
391
392void processAddBytesOverflow(uint8_t *bytes, uint32_t len) {
393 for (uint32_t i = 0; i < len; i++) {
394 bytes[0] += bytes[i];
395 }
396}
397
398Do THIS instead:
399
400void processAddBytesOverflow(void *input, uint32_t len) {
401 uint8_t *bytes = input;
402
403 for (uint32_t i = 0; i < len; i++) {
404 bytes[0] += bytes[i];
405 }
406}
407
408The input types to your functions describe the interface to your code, not what your code is doing with the parameters. The interface to the code above means "accept a byte array and a length", so you don't want to restrict your callers to only uint8_t byte streams. Maybe your users even want to pass in old-style char * values or something else unexpected.
409
410By declaring your input type as void * then re-assigning or re-casting to the actual type you want inside your function, you save the users of your function from having to think about abstractions inside your own library.
411
412Some readers have pointed out alignment problems with this example, but we are accessing single byte elements of the input, so everything is fine. If instead we were casting the input to wider types, we would need to watch out for alignment issues. For a different write up dealing with cross-platform alignment issues, see Unaligned Memory Access. (reminder: this page of generic overview details isn't about cross-architecture intricacies of C, so external knowledge and experience is expected to fully use any examples provided.)
413Return Parameter Types
414
415C99 gives us the power of <stdbool.h> which defines true to 1 and false to 0.
416
417For success/failure return values, functions should return true or false, not an int32_t return type with manually specifying 1 and 0 (or worse, 1 and -1 (or is it 0 success and 1 failure? or is it 0 success and -1 failure?)).
418
419If a function mutates an input parameter to the extent the parameter is invalidated, instead of returning the altered pointer, your entire API should force double pointers as parameters anywhere an input can be invalidated. Coding with "for some calls, the return value invalidates the input" is too error prone for mass usage.
420
421So, do NOT do this:
422
423void *growthOptional(void *grow, size_t currentLen, size_t newLen) {
424 if (newLen > currentLen) {
425 void *newGrow = realloc(grow, newLen);
426 if (newGrow) {
427 /* resize success */
428 grow = newGrow;
429 } else {
430 /* resize failed, free existing and signal failure through NULL */
431 free(grow);
432 grow = NULL;
433 }
434 }
435
436 return grow;
437}
438
439Do THIS instead:
440
441/* Return value:
442 * - 'true' if newLen > currentLen and attempted to grow
443 * - 'true' does not signify success here, the success is still in '*_grow'
444 * - 'false' if newLen <= currentLen */
445bool growthOptional(void **_grow, size_t currentLen, size_t newLen) {
446 void *grow = *_grow;
447 if (newLen > currentLen) {
448 void *newGrow = realloc(grow, newLen);
449 if (newGrow) {
450 /* resize success */
451 *_grow = newGrow;
452 return true;
453 }
454
455 /* resize failure */
456 free(grow);
457 *_grow = NULL;
458
459 /* for this function,
460 * 'true' doesn't mean success, it means 'attempted grow' */
461 return true;
462 }
463
464 return false;
465}
466
467Or, even better, Do THIS instead:
468
469typedef enum growthResult {
470 GROWTH_RESULT_SUCCESS = 1,
471 GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY,
472 GROWTH_RESULT_FAILURE_ALLOCATION_FAILED
473} growthResult;
474
475growthResult growthOptional(void **_grow, size_t currentLen, size_t newLen) {
476 void *grow = *_grow;
477 if (newLen > currentLen) {
478 void *newGrow = realloc(grow, newLen);
479 if (newGrow) {
480 /* resize success */
481 *_grow = newGrow;
482 return GROWTH_RESULT_SUCCESS;
483 }
484
485 /* resize failure, don't remove data because we can signal error */
486 return GROWTH_RESULT_FAILURE_ALLOCATION_FAILED;
487 }
488
489 return GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY;
490}
491
492Formatting
493
494Coding style is simultaneously very important and utterly worthless.
495
496If your project has a 50 page coding style guideline, nobody will help you. But, if your code isn't readable, nobody will want to help you.
497
498The solution here is to always use an automated code formatter.
499
500The only usable C formatter as of 2016 is clang-format. clang-format has the best defaults of any automatic C formatter and is still actively developed.
501
502Here's my preferred script to run clang-format with good parameters:
503
504#!/usr/bin/env bash
505
506clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@"
507
508Then call it as (assuming you named the script cleanup-format):
509
510matt@foo:~/repos/badcode% cleanup-format -i *.{c,h,cc,cpp,hpp,cxx}
511
512The -i option overwrites existing files in place with formatting changes instead of writing to new files or creating backup files.
513
514If you have many files, you can recursively process an entire source tree in parallel:
515
516#!/usr/bin/env bash
517
518# note: clang-tidy only accepts one file at a time, but we can run it
519# parallel against disjoint collections at once.
520find . \( -name \*.c -or -name \*.cpp -or -name \*.cc \) |xargs -n1 -P4 cleanup-tidy
521
522# clang-format accepts multiple files during one run, but let's limit it to 12
523# here so we (hopefully) avoid excessive memory usage.
524find . \( -name \*.c -or -name \*.cpp -or -name \*.cc -or -name \*.h \) |xargs -n12 -P4 cleanup-format -i
525
526Now, there's a new cleanup-tidy script there. The contents of cleanup-tidy is:
527
528#!/usr/bin/env bash
529
530clang-tidy \
531 -fix \
532 -fix-errors \
533 -header-filter=.* \
534 --checks=readability-braces-around-statements,misc-macro-parentheses \
535 $1 \
536 -- -I.
537
538clang-tidy is policy driven code refactoring tool. The options above enable two fixups:
539
540 readability-braces-around-statements — force all if/while/for statement bodies to be enclosed in braces
541 It's an accident of history for C to allow "brace optional" single statements after loop constructs and conditionals. It is inexcusable to write modern code without braces enforced on every loop and every conditional. Trying to argue "but, the compiler accepts it!" has nothing to do with the readability, maintainability, understandability, or skimability of code. You aren't programming to please your compiler, you are programming to please future people who have to maintain your current brain state years after everybody has forgotten why anything exists in the first place.
542 misc-macro-parentheses — automatically add parens around all parameters used in macro bodies
543
544clang-tidy is great when it works, but for some complex code bases it can get stuck. Also, clang-tidy doesn't format, so you need to run clang-format after you tidy to align new braces and reflow macros.
545Readability
546
547the writing seems to start slowing down here...
548Comments
549
550logical self-contained portions of code file
551File Structure
552
553Try to limit files to a max of 1,000 lines (1,500 lines in really bad cases). If your tests are in-line with your source file (for testing static functions, etc), adjust as necessary.
554misc thoughts
555Never use malloc
556
557You should always use calloc. There is no performance penalty for getting zero'd memory. If you don't like the function protype of calloc(object count, size per object) you can wrap it with #define mycalloc(N) calloc(1, N).
558
559Readers have commented on a few things here:
560
561 calloc does have a performance impact for huge allocations
562 calloc does have a performance impact on weird platforms (minimal embedded systems, game consoles, 30 year old hardware, ...)
563 wrapping calloc(element count, size of each element) is not always a good idea.
564 a good reason to avoid malloc() is it can't check for integer overflow and is a potential security risk
565 calloc allocations remove valgrind's ability to warn you about unintentional reads or copies of uninitialized memory since allocations get initialized to 0 automatically
566
567Those are good points, and that's why we always must do performance testing and regression testing for speed across compilers, platforms, operating systems, and hardware devices.
568
569One advantage of using calloc() directly without a wrapper is, unlike malloc(), calloc() can check for integer overflow because it multiplies its arguments together to obtain your final allocation size. If you are only allocating tiny things, wrapping calloc() is fine. If you are allocating potentially unbounded streams of data, you may want to retain the regular calloc(element count, size of each element) calling convention.
570
571No advice can be universal, but trying to give exactly perfect generic recommendations would end up reading like a book of language specifications.
572
573For references on how calloc() gives you clean memory for free, see these nice writeups:
574
575 Benchmarking fun with calloc() and zero pages (2007)
576 Copy-on-write in virtual memory management
577
578I still stand by my recommendation of always using calloc() for most common scenarios of 2016 (assumption: x64 target platforms, human-sized data, not including human genome-sized data). Any deviations from "expected" drag us into the pit of despair of "domain knowledge," which are words we shan't speak this day.
579
580Subnote: The pre-zero'd memory delivered to you by calloc() is a one-shot deal. If you realloc() your calloc() allocation, the grown memory extended by realloc is not new zero'd out memory. Your grown allocation is filled with whatever regular uninitialized contents your kernel provides. If you need zero'd memory after a realloc, you must manually memset() the extent of your grown allocation.
581Never memset (if you can avoid it)
582
583Never memset(ptr, 0, len) when you can statically initialize a structure (or array) to zero (or reset it back to zero by assigning from an in-line compound literal or by assigning from a global zero'd out structure).
584
585Though, memset() is your only choice if you need to zero out a struct including its padding bytes (because {0} only sets defined fields, not undefined offsets filled by padding).
586Learn More
587
588Also see Fixed width integer types (since C99)
589
590Also see Apple's Making Code 64-Bit Clean
591
592Also see the sizes of C types across architectures — unless you keep that entire table in your head for every line of code you write, you should use explicitly defined integer widths and never use char/short/int/long built-in storage types.
593
594Also see size_t and ptrdiff_t
595
596Also see Secure Coding. If you really want to write everything perfectly, simply memorize their thousand simple examples.
597
598Also see Modern C by Jens Gustedt at Inria.
599
600Also see Understanding Character/String Literals in C/C++ for details about Unicode support in C11.
601Closing
602
603Writing 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.
604
605The best we can do is write simple, understandable code with as few indirections and as little undocumented magic as possible.
606
607-Matt — @mattsta — â˜mattsta
608Attributions
609
610This made the twitter and HN rounds, so many people helpfully pointed out flaws or biased thoughts I'm promulgating here.
611
612First up, Jeremy Faller and Sos Sosowski and Martin Heistermann and a few other people were kind enough to point out my memset() example was broken and provided the proper fix.
613
614Martin Heistermann also pointed out the localThing = localThingNull example was broken.
615
616The opening quote about not writing C if you can avoid it is from the wise internet sage @badboy_.
617
618Remi Gacogne pointed out I forgot -Wextra.
619
620Levi Pearson pointed out gcc-5 defaults to gnu11 instead of c89 as well as clarifying the default clang mode.
621
622Christopher pointed out the -O2 vs -O3 section could use a little more clarification.
623
624Chad Miller pointed out I was being lazy in the clang-format script params.
625
626Many people also pointed out the calloc() advice isn't always a good idea if you have extreme circumstances or non-standard hardware (examples of bad ideas: huge allocations, allocations on embedded jiggers, allocations on 30 year old hardware, etc).
627
628Charles Randolph pointed out I misspelled the world "Building."
629
630Sven Neuhaus pointed out kindly I also do not possess the ability to spell "initialization" or "initializers." (and also pointed out I misspelled "initialization" wrong the first time here as well)
631
632Colm MacCárthaigh pointed out I forgot to mention #pragma once.
633
634Jeffrey Yasskin pointed out we should kill strict aliasing too (mainly a gcc optimization).
635
636Jeffery Yasskin also provided better wording around the -fno-strict-aliasing section.
637
638Chris Palmer and a few others pointed out calloc-vs-malloc parameter advantages and the overall drawback of writing a wrapper for calloc() because calloc() provides a more secure interface than malloc() in the first place.
639
640Damien Sorresso pointed out we should remind people realloc() doesn't zero out grown memory after an initial zero'd calloc() request.
641
642Pat Pogson pointed out I was unable to spell the word "declare" correctly as well.
643
644@TopShibe pointed out the stack-allocated initialization example was wrong because the examples I gave were global variables. Updated wording to just mean "auto-allocated" things, be it stack or data sections.
645
646Jonathan Grynspan suggested harsher wording around the VLA example because they are dangerous when used incorrectly.
647
648David O'Mahony kindly pointed out I can't spell "specify" either.
649
650Dr. David Alan Gilbert pointed out ssize_t is a POSIXism and Windows doesn't have it.
651
652Chris Ridd suggested we explicitly mention C99 is C from 1999 and C11 is C from 2011 because otherwise it looks strange having 11 be newer than 99.
653
654Chris Ridd also noticed the clang-format example used unclear naming conventions and suggested better consistency across examples.
655
656Anthony Le Goff pointed us to a book-length treatment of many modern C ideas called Modern C.
657
658Stuart Popejoy pointed out my inaccurate spelling of deliberately was truly inaccurate.
659
660jack rosen pointed out my usage of the word 'exists' does not mean 'exits' as I intended.
661
662Jo Booth pointed out I like to spell compatibility as compatibility, which seems more logical, but English commonality disagrees.
663
664Stephen Anderson decoded my aberrant spelling of 'stil' back into 'still.'
665
666Richard Weinberger pointed out struct initialization with {0} doesn't zero out padding bytes, so sending a {0} struct over the wire can leak unintended bytes on under-specified structs.
667
668@JayBhukhanwala pointed out the function comment in Return Parameter Types was inaccurate because I didn't update the comment when the code changed (story of our lives, right?).
669
670Lorenzo pointed out we should explicitly provide a warning concerning potential cross-platform alignment issues in section Parameter Types.
671
672Paolo G. Giarrusso re-clarified the alignment warning I previously added to be more correct regarding the examples given.
673
674Fabian Klötzl provided the valid struct compound literal assignment example since it's perfectly valid syntax I just hadn't run across before.
675
676Omkar Ekbote provided a very thorough walkthrough of typos and consistency problems here including that I couldn't spell "platform," "actually," "defining," "experience," "simultaneously," "readability," as well as noted some other unclear wordings.
677
678Carlo Bellettini fixed my aberrant spelling of the word aberrant.
679
680Keith S Thompson provided many technical corrections in his great article how-to-c-response.
681
682Marc Bevand pointed out we should say the fprintf type specifiers come from inttypes.h.
683
684Brian Cain pointed out we should mention the fast and least types too.
685
686Michal Marzec kindly pointed out I still can't spell anonymous.
687
688Nick Galbreath used misspell to discover four more sneaky misspellings.
689
690Many people on reddit went apeshit because this article originally had #import somewhere by mistake. Sorry, crazy people, but this started out as an unedited and unreviewed year old draft when originally pushed live. The error has since been remedied.
691
692Some people also pointed out the static initialization example uses globals which are always initialized to zero by default anyway (and that they aren't even initialized, they are statically allocated). This is a poor choice of example on my part, but the concepts still stand for typical usage within function scopes. The examples were meant to be any generic "code snippet" and not necessarily top level globals.
693
694A few people seem to have read this as an "I hate C" page, but it isn't. C is dangerous in the wrong hands (not enough testing, not enough experience when widely deployed), so paradoxically the two kinds of C developers should only be novice hobbyists (code failure causes no problems, it's just a toy) or people who are willing to test their asses off (code failure causes life or financial loss, it's not just a toy) should be writing C code for production usage. There's not much room for "casual observer C development." For the rest of the world, that's why we have Erlang.
695
696Many people have also mentioned their own pet issues as well or issues beyond the scope of this article (including new C11 only features like George Makrydakis reminding us about C11 generic abilities).
697
698Perhaps another article about "Practical C" will show up to cover testing, profiling, performance tracing, optional-but-useful warning levels, etc.
699
700series
701swift: power of types
702quit job, do travel
703personal
704my codestartups: rage reviewhowto c (2016)think you can const?email security rulesdisrupt interviewsprogrammer streetkoshaboutiOS appsheroin docsRSS
705future
706searching (past)errors at scaleemployees