Advanced C Programming

Autumn 2015 :: ECE 264 :: Purdue University

This is for Fall 2015 (9 years ago)

Code quality standards

In this class, you will learn to write good code. That takes a different kind of skill and effort than just making a small homework assignment work. However, good code is easier to read, debug, and maintain. It also helps you avoid creating bugs in the first places.

The rules in this document are adapted from a variety of sources. While some might disagree on a few small points, most of these reflect prevailing views on C code style.

Summary

1. global constants all caps with underscores
2. comments always explain complex code; never state the obvious (e.g., // main function)
3. struct names camel case with first letter capitalized (e.g., PersonNode)
4. internal helper functions name starts with underscore (e.g., _sort_helper(…)); do not declare .h file
5. curly braces for if/for/… always
6. indent body of function/if/for/… always
7. multiple statements on one line never
8. dead code never
9. useless variables never
10. vim modeline always; must match the tab settings you have been using
11. constants declare with const not #define (usually)
12. variable names descriptive (usually)
13. NULL vs. 0 vs. '\0' NULL for addresses, 0 for numeric values, '\0' for characters
14. declarations always initialize in declaration (usually)
15. boolean values use true/false from stdbool.h; never use 1/0 or true/false
16. array initializers use where possible/appropriate (usually)
17. struct initializers use where possible/appropriate (usually)
18. data type sizes use sizeof; never make assumptions
19. printf use only for "official" output
20. variableNames vs. variable_name pick one of these two and be consistent
21. curly braces for if blocks be consistent; use one of the two styles (see below)
22. curly braces for functions be consistent; use one of the two styles (see below)
23. indent type be consistent; tabs or spaces; any tab size; declare tab size in vim modeline
24. spacing between functions be consistent; one or two spaces between functions
25. sizeof argument must be a variable name when used in an initialization or malloc(..)
This was announced 10/15 and added here 10/20. It is optional for hw06 but required thereafter.

In case of any inconsistency, the descriptions below take precedent over this table.

Required

Global constants

Rule:  Use all caps with words separated by underscores
Reason:  This makes code more readable by making it clear which variables might change.
Good: 
const char* s = COURSE_NAME;

Comments

Rule:  Comment code that is complex, or where the purpose of a block of code would be unclear at first glance. Do not include comments for things that are extremely obvious.
Note: This is consistent with Google's C++ style guide under Implementation Comments.

Struct names

Rule:  Use camel case with the first letter capitalized
Reason:  This makes types look very different from instances of those types.
Good: 
struct ListNode{ … };
Good: 
struct Point{ … };
Note: This is consistent with Google's C++ style guide under Type Names.

Internal helper functions or global constants

Rule:  Begin with an underscore.
Reason:  This warns readers of your code (including you) that the meaning may be hard to understand. It also lets them know that it may change, so it's best not to refer to it from elsewhere.
Good: 
void _do_something_mysterious() { }
Good: 
const int _MAX_BUFFER_SIZE = 128;
Note:  "Internal" means things that should not be called from outside the current file or context. This rule is more important for larger projects, and is pointless for projects that have only a single file. However, real projects tend to grow, so it is a good habit to build on.

Braces for if, for, while, switch, do, …

Rule:  Always required. (Do not use inside each case of a switch, but use for the switch itself.)
Reason:  Even though C will let you omit the braces, if the body is just one line, it
Good: 
if(a < 1) { foo(); }
Bad: 
if(a < 1) foo();
Note: Apple had a major security bug due to someone omitting the braces (article).

indent body of function/if/for/while/do/switch

Rule:  Body of a function definition or if/for/while/do/switch statement must be indented, relative to the lines that contain the enclosing curly braces.
Exception:  In rare cases, you might want a loop or function with an empty body. In those cases, you may have both braces on the same line as the beginning of the loop. Also, note that this rule does not apply to struct or array initializers.
Good: 
for(int i=0; i<5; i++) { printf("i == %d\n", i); }
Good: 
for(int i=0; i<5; i++) { printf("i == %d\n", i); }
Good:  (or at least acceptable)
if(a == 0) { foo(); } else { bar(); }
Bad: 
for(int i=0; i<5; i++) { printf("i == %d\n", i); }
Bad: 
for(int i=0; i<5; i++) { { printf("i == %d\n", i); }
Bad: 
for(int i=0; i<5; i++) { { printf("i == %d\n", i); }
Bad: 
for(int i=0; i<5; i++) { printf("i == %d\n", i); }
Bad: 
for(int i=0; i<5; i++) { printf("i == %d\n", i); }
Good: (ugly, but marginally acceptable, at least in ECE 264)
for(; s != '\0'; s++) { }

Multiple statements on one line

Rule:  Not allowed
Reason:  It makes code unnecessarily hard to read.
Good #1: 
int a = 1; int b = 2;
Good #2: 
int a = 1; char c = 'c';
Good #3:  (ugly, but marginally acceptable, at least in ECE 264)
for(; *s != '\0'; s++) {}
Bad #1: 
int a = 1; char c = 'c';
Bad #2: 
for(int i=0; i < 5; i++) { printf("i == %d"); }

Dead code

Rule:  Not allowed
Reason:  This makes code harder to debug by concealing potential problems.
Good: 
int main() { // ... } void _foo() { // ... }
Good: 
if(1 == 0) { doSomething(); // will never get here!!! }
Note:  "Dead code" is code that is never called.

Useless variables

Rule:  Not allowed
Reason:  This makes code harder to debug by concealing potential problems.
Bad: 
int foo(int a) { int b = 1; // never used!!! return a * 10; }

Useless assignments

Rule:  Not allowed
Reason:  This makes code harder to debug by concealing potential problems.
Bad: 
int foo() { int a = 0; a = a + 10; // no effect!!! a = 20; return a * 10; }
Note:  Useless assignments are assignments that cannot, under any circumstances, have any effect on the output.
Exception:  This does not apply variables that are initialized to 0 or NULL (or similar).

Vim modeline:

Rule:  Required
Reason:  This ensures that when course staff in ECE 264 view your code, they will have the same indent type that you had. It's also a good habit when working on projects where others might be using Vim (outside of this class).
Good:  (for those who prefer tabs)
/* vim: set tabstop=4 shiftwidth=4 fileencoding=utf-8 noexpandtab: */
Good:  (for those who prefer spaces)
/* vim: set tabstop=4 shiftwidth=4 fileencoding=utf-8 expandtab: */

Constants

Rule:  Declare with const, not #define
Reason:  const enforces types. #define just does a simple text substitution.
Good: 
const int NUM_ASSIGNMENTS = 15;
Bad: 
#define NUM_ASSIGNMENTS 15
Exception:  It's okay to use #define for constants if you have a very specific reason. For ECE 264, you must document the reason in a comment.
Note: This is consistent with Google's C++ style guide under Use of const.

Variable names

Rule:  Must describe contents
Reason:  This makes code more readable, especially when you come back to it later or show it to someone who didn't read it (e.g., when asking for help).
Exception:  For array indices or other variables that are used only in a small portion of the code where the meaning is completely obvious at first glance, it is okay to use one or two letters (e.g., i for an index, s or a string, ch for a string, n for some other number). Also, short names are okay for small examples to illustrate programming concepts where brevity is a goal in itself.
Note: This is consistent with Google's C++ style guide under General Naming Rules.

NULL vs. 0 vs. '\0'

Rule:  Use NULL for addresses (i.e., null pointers), 0 for numeric values, and '\0' for characters (e.g., string terminator).
Reason:  This makes code more readable and avoids bugs.
Note:  Internally, NULL, 0, and '\0' are all stored as 0.
Reference: This is also part of Google's C++ style guide under 0 and nullptr/NULL.

Declarations

Rule:  Declarations initialize
Good: 
int i = 0;
Bad: 
int i; i = 0;
Note: This is consistent with Google's C++ style guide under Local variables.

Boolean values

Rule:  Do not use 1 and 0 for true and false. Instead, use the true and false values and the bool type from the stdbool.h library. We are using the C99 version of the C language standard in this class, which includes stdbool.
Note:  We are using the C99 version of the C language standard in this class, which includes stdbool.
Good: 
bool is_positive = (a > 0);
Bad: 
int is_positive = (a > 0);
Reason:  The constants true and false make your code more readable because express what you mean. Since stdbool.h is part of the standard library, they will be readable by others and interoperable with other code.

Array initializers

Rule:  Use where possible (and appropriate)
Good: 
int numbers[] = {1, 2, 3};
Bad: 
int numbers[3]; numbers[0] = 1; numbers[1] = 2; numbers[2] = 3;
Reason:  Array initializers are clearer to read, and avoid certain types of bugs (e.g., numbers[3] = 4 for an array of size 3.).
Note: This is consistent with Google's C++ style guide under Local variables.

Struct initializers

Rule:  Use where possible (and appropriate)
Good: 
struct Point point1 = {.x = 1, .y = 2};
Bad #1: 
struct Point point1; point1.x = 1; point1.y = 2;
Bad #2: 
struct Point point1 = {1, 2};
Reason:  Struct initializers are clearer to read. They also prevent mistakes where programmers sometimes forget to initialize one field, or perhaps edit the code later and inadvertently delete part of the initialization. Bad #2 is actually fine in earlier versions of c (e.g. c89), but the new syntax shown as Good prevents errors where you get the order wrong.
Note: This was added 10/1/2015.

Data type sizes

Rule:  Do not make assumptions about the size of any data type. Use the sizeof(…) operator instead.
Good: 
char* s = malloc(n * sizeof(*s));
Bad: 
int* array = malloc(n * 4);

printf

Rule:  Use printf only for "official" output. For debugging, use #define to create a log macro that you can turn off with a flag to the compiler (i.e., conditional compilation).
Good: 
#ifdef DBG_LOG #define dbg_log(...) printf(__VA_ARGS__) #else #define dbg_log #endif int main() { dbg_log("Starting...") }
Bad: 
int main() { printf("Starting...") }

assert

Rule:  Use assert only to catch bugs in your code. Do not use assert to catch anything that the user could control, external issues with the system, or anything else outside of your code.

Your choice - pick one and be consistent within a given project

Multi-word variable and function names

Option A: 
int list_idx = 0;
Option B:  lowercase camel case
int listIdx = 0;

Braces for if blocks

Option A: 
if(a == 0) { }
Option B: 
if(a == 0) { }

Braces for functions

Option A: 
int foo() { }
Option B: 
int foo() { }

Indent type

Option A:  tabs
Option B:  spaces
Note:  This must be reflected in your vim modeline (see above). Also, you are welcome to choose a different tab size (i.e., instead of 4).
Reason:  If you mix tabs and spaces, your file layout may be hard to read for others with a different tabstop setting.

Spacing

Option A:  one space between functions
Option B:  two spaces between functions

Updates

9/24: Added links to Google C++ Style Guide; added reason for using stdbool.h; no substantive changes to code standards

10/7: Added section on indent body of function/if/for/while/do/switch. Proper indenting was previously discussed in lecture and practiced on HW01.