Style Guide

adapted from COMP1511's style guide

Auto-Formatting

One of the easiest ways to ensure your code is formatted consistently is to use an automatic formatter. The most commonly used one for C/C++ is clang-format, which is available on CSE machines and can be installed on your local machine.

To use clang-format you'll need to download our .clang-format file (available here) and place it in your home directory. Important: Make sure the file is named .clang-format (with the dot!!!), or the commands below will not work.

Then, you can use the following command to format your code:

clang-format -style=file source-file...

where source-file is the file(s) you want to format. This will output the formatted code to stdout. To format the file(s) in-place, use the -i option:

clang-format -i -style=file source-file...

Note that clang-format is not perfect and may occasionally produce oddly-formatted code, so you must still check your code manually. Also, the code produced by clang-format may not be the only acceptable way to format your code - you can still tweak the formatting so that it is more aesthetically pleasing to you (as long as it still follows the style guide). You can also edit the .clang-format file to suit your preferences, as long as the resulting formatting follows the style guide.

If you're curious about the meaning of the configuration options in .clang-format you can learn about them (and more) here.

Level-Up with VSCode!

If you use VSCode you can use keyboard shortcuts to format your code with clang-format. Learn more here.

Note: We do not recommend configuring your editor to format on save because, as mentioned above, clang-format may occasionally produce oddly-formatted code.

Code Layout

Code Layout

Use the following order for laying out your code:

  1. Header comment
  2. Inclusion of system header files
  3. Inclusion of local header files
  4. Definition of constants (#defines and enums)
  5. Definition of data types (structs and typedefs)
  6. Function prototypes
  7. main() function
  8. Function definitions
Code Layout

Use the following order for laying out your code:

  1. Header comment
  2. Inclusion of system header files
  3. Inclusion of local header files
  4. Definition of constants (#defines and enums)
  5. Definition of data types (structs and typedefs)
  6. Function prototypes
  7. main() function
  8. Function definitions
// COMP2521 Lab 10 Exercise - Turing's test
//
// Pretend to be a human in text-based conversation.
// https://en.wikipedia.org/wiki/Turing_test
//
// Authors:
// Grace Hopper (z1234567@unsw.edu.au)
// Ada Lovelace (z5000000@unsw.edu.au)
//
// Written: 19/03/2019

#include <math.h>
#include <stdio.h>
#include <string.h>

#include "language.h"

#define MEANING_OF_LIFE 42

// struct definitions would go here

// function prototypes would go here


int main(void) {
    printf("Hello!\n");
    return 0;
}

// function definitions would go here

Header Comment

All submitted files should have a header comment.

The header comment should contain at least:

  • The names of the author(s) - include your UNSW zID with any code you submit at UNSW
  • The date it was written
  • A description of what the program does

For larger programs such as assignments, also include:

  • An overview of how the program works, including any bugs or limitations
  • Any other information that might help a reader understand the code
  • References to resources used in constructing the code

It is not necessary to include licensing information, but it is commonly present in header comments of code outside uni.

Header Comment

All submitted files should have a header comment.

The header comment should contain at least:

  • The names of the author(s) - include your UNSW zID with any code you submit at UNSW
  • The date it was written
  • A description of what the program does

For larger programs such as assignments, also include:

  • An overview of how the program works, including any bugs or limitations
  • Any other information that might help a reader understand the code
  • References to resources used in constructing the code

It is not necessary to include licensing information, but it is commonly present in header comments of code outside uni.

// COMP2521 Lab 10 Exercise - Turing's test
//
// Pretend to be a human in text-based conversation.
// https://en.wikipedia.org/wiki/Turing_test
//
// Authors:
// Grace Hopper (z1234567@unsw.edu.au)
// Ada Lovelace (z5000000@unsw.edu.au)
//
// Written: 19/03/2019
Braces

Braces must always be used with if, else, while, for, do ... while and switch statements, except (optionally) in cases described below.

Compact braces should be used, which means:

  • No line break before the opening brace.
  • Line break after the opening brace.
  • Line break before the closing brace.
  • Line break after the closing brace, except when it is followed by else or when it is followed by the while keyword in a do ... while statement.
  • There is an exception for the previous point - for long conditions (of if, while, for and do ... while statements) and functions that have long parameter lists (which span multiple lines), the opening brace may appear on its own line.

It is acceptable to omit braces in an if statement if the body is short and on the same line as the condition, and there is no proceeding else if or else.

Braces

Braces must always be used with if, else, while, for, do ... while and switch statements, except (optionally) in cases described below.

Compact braces should be used, which means:

  • No line break before the opening brace.
  • Line break after the opening brace.
  • Line break before the closing brace.
  • Line break after the closing brace, except when it is followed by else or when it is followed by the while keyword in a do ... while statement.
  • There is an exception for the previous point - for long conditions (of if, while, for and do ... while statements) and functions that have long parameter lists (which span multiple lines), the opening brace may appear on its own line.

It is acceptable to omit braces in an if statement if the body is short and on the same line as the condition, and there is no proceeding else if or else.

For functions, the opening brace should be on the same line as the function signature (unless the function has a long parameter list which spans multiple lines, in which case the opening brace may appear on its own line.) The closing brace should be on its own line and not indented.

For if statements, the opening brace should be on the same line as the if and the condition. The closing brace should be on the same line as the following else, if there is one, or on its own line otherwise, and should be indented to the same level as the matching if.

For while and for loops, the opening brace should be on the same line as the loop condition (unless the condition is long and spans multiple lines, in which case the opening brace may appear on its own line). The closing brace should be on its own line and should be indented to the same level as the matching while/for.

For do ... while loops, the opening brace should be on the same line as the do. The closing brace should be on the same line as the while, and should be indented to the same level as the do.

For switch statements, the opening brace should be on the same line as the switch. The closing brace should be on its own line, and should be indented to the same level as the switch.

Braces

Braces must always be used with if, else, while, for, do ... while and switch statements, except (optionally) in cases described below.

Compact braces should be used, which means:

  • No line break before the opening brace.
  • Line break after the opening brace.
  • Line break before the closing brace.
  • Line break after the closing brace, except when it is followed by else or when it is followed by the while keyword in a do ... while statement.
  • There is an exception for the previous point - for long conditions (of if, while, for and do ... while statements) and functions that have long parameter lists (which span multiple lines), the opening brace may appear on its own line.

It is acceptable to omit braces in an if statement if the body is short and on the same line as the condition, and there is no proceeding else if or else.

int someFunction(int a, int b) {
    if (a > b) {
        // do something
    } else if (a < b) {
        // do something
    } else {
        // do something
    }

    int i = 0;
    while (i <= 10) {
        // do something
        i++;
    }
    
    for (int j = 10; j >= 0; j--) {
    	// do something
    }
    
    int k = 0;
    do {
        // do something
        k++;
    } while (k <= 10);

    if (a == 0) return 0;
}

void functionWithManyParameters(int a, int b, int c, int d, int e, int f,
                                int g, int h)
{
    // do something
}
Braces

Braces must always be used with if, else, while, for, do ... while and switch statements, except (optionally) in cases described below.

Compact braces should be used, which means:

  • No line break before the opening brace.
  • Line break after the opening brace.
  • Line break before the closing brace.
  • Line break after the closing brace, except when it is followed by else or when it is followed by the while keyword in a do ... while statement.
  • There is an exception for the previous point - for long conditions (of if, while, for and do ... while statements) and functions that have long parameter lists (which span multiple lines), the opening brace may appear on its own line.

It is acceptable to omit braces in an if statement if the body is short and on the same line as the condition, and there is no proceeding else if or else.

// BAD - the code between the pairs of braces should be indented

if (i % 2 == 0) {
printf("even");
} else {
printf("odd");
}

// BAD - move to a new line after {, and before and after }

while (i < 0) { i++; }

// BAD - always use braces with if statements
// (unless the body is short and on the same line as the condition,
// and there is no proceeding else if or else)

if (i < 0)
    i++;

if (i < 0) i = -1;
else if (i > 0) i = 1;
else i = 0;

// BAD - closing braces don't line up

if (a > b) {
    // do something
    } else if (b < a) {
        // do something
        } else {
            // do something
            }
Indentation

Each indentation level must be four spaces or one tab.

Between any pair of braces, the indentation level should increase by one stop. You may use either tabs or spaces for indentation, but do not use both approaches in the same program.

For long expressions that span multiple lines, the indentation level of each line (except the first) should increase by two stops or by an amount that improves readability (e.g., aligning the arguments of long function calls).

Indentation

Each indentation level must be four spaces or one tab.

Between any pair of braces, the indentation level should increase by one stop. You may use either tabs or spaces for indentation, but do not use both approaches in the same program.

For long expressions that span multiple lines, the indentation level of each line (except the first) should increase by two stops or by an amount that improves readability (e.g., aligning the arguments of long function calls).

if (something && somethingElse && somethingElseAgain &&
	anotherThing && evenMoreThings && somethingElseEntirely &&
	thereAreTooManyConditions && thisLineIsTooLong)
{
    ...
}

functionWithManyArguments(something, somethingElse,
                          somethingElseAgain, anotherThing,
                          evenMoreThings);
Nesting Depth

Your code should not exceed 4 levels of nesting (layers of curly brackets). If your code is exceeding this level of nesting, then it is probably a sign that you should look to simplify your approach, or try to move some of the logic into a function!

Nesting Depth

Your code should not exceed 4 levels of nesting (layers of curly brackets). If your code is exceeding this level of nesting, then it is probably a sign that you should look to simplify your approach, or try to move some of the logic into a function!

// This is good
int main(void) {
	int i = 0;
	if (i > 0 && i < 3) {
		doSomethingComplicated(i);
	}
}

void doSomethingComplicated(int i) {
	int j = 0;
	while (j < i) {
		if (j % 2 == 0) {
			printf("Hello!\n");
		}
	}
}
Nesting Depth

Your code should not exceed 4 levels of nesting (layers of curly brackets). If your code is exceeding this level of nesting, then it is probably a sign that you should look to simplify your approach, or try to move some of the logic into a function!

// This is bad style
int main(void) {
	int i = 0;
	if (i > 0) {
		if (i < 3) {
			int j = 0;
			while (j < i) {
				if (j % 2 == 0) {
					printf("Hello!\n");
				}
			}
		}
	}
}
Horizontal Spacing

Include a space after keywords such as:

if, else, while, for, do, return

Include a space on each side of binary operators such as:

=  +  -  *  /  %  <  >  <=  >=  ==  !=  &&  ||

Do not include a space after prefix unary operators such as:

& * !

Do not include a space before postfix unary operators such as:

++ --

Do not include a space on each side of member access operators such as:

. ->

Do not include a space between a set of parentheses and the expression within them.

When declaring pointer types, the * should always be attached to the variable name rather than the type. For example:

int *ptr;
int **ptr;
Horizontal Spacing

Include a space after keywords such as:

if, else, while, for, do, return

Include a space on each side of binary operators such as:

=  +  -  *  /  %  <  >  <=  >=  ==  !=  &&  ||

Do not include a space after prefix unary operators such as:

& * !

Do not include a space before postfix unary operators such as:

++ --

Do not include a space on each side of member access operators such as:

. ->

Do not include a space between a set of parentheses and the expression within them.

When declaring pointer types, the * should always be attached to the variable name rather than the type. For example:

int *ptr;
int **ptr;
// This is correct:
if (a + b > c) {
    i++;
    curr = curr->next;
}
int *ptr;
Horizontal Spacing

Include a space after keywords such as:

if, else, while, for, do, return

Include a space on each side of binary operators such as:

=  +  -  *  /  %  <  >  <=  >=  ==  !=  &&  ||

Do not include a space after prefix unary operators such as:

& * !

Do not include a space before postfix unary operators such as:

++ --

Do not include a space on each side of member access operators such as:

. ->

Do not include a space between a set of parentheses and the expression within them.

When declaring pointer types, the * should always be attached to the variable name rather than the type. For example:

int *ptr;
int **ptr;
// This is *not* correct:
if( a+b>c ) {
    i ++;
    curr = curr -> next;
}
int* ptr;
Vertical Spacing

Use vertical whitespace (blank lines between code) occasionally to indicate where the related parts of your code are.

There should always be at least one blank line between functions.

Within functions:

  • Not having a blank line between two lines of code indicates that the lines should be understood together, or are closely related, or are part of the same "step".
  • Using exactly one blank line indicates that two sections of code are distinct - they split your code into "paragraphs".
  • Using more than one blank line between two lines of code is mostly unnecessary, except when splitting a file into related sections.

The exact use of vertical whitespace is subjective, and your tutor will be able to give you feedback on your style throughout the term. Vertical whitespace is like chocolate -- some of it significantly improves your life, but too much can also be its own problem; and used in the wrong place it can be very confusing.

Vertical Spacing

Use vertical whitespace (blank lines between code) occasionally to indicate where the related parts of your code are.

There should always be at least one blank line between functions.

Within functions:

  • Not having a blank line between two lines of code indicates that the lines should be understood together, or are closely related, or are part of the same "step".
  • Using exactly one blank line indicates that two sections of code are distinct - they split your code into "paragraphs".
  • Using more than one blank line between two lines of code is mostly unnecessary, except when splitting a file into related sections.

The exact use of vertical whitespace is subjective, and your tutor will be able to give you feedback on your style throughout the term. Vertical whitespace is like chocolate -- some of it significantly improves your life, but too much can also be its own problem; and used in the wrong place it can be very confusing.

int main(void) {
	...

	mergeSort(arr, 0, size - 1);
}

static void mergeSort(int a[], int lo, int hi) {
	if (lo >= hi) return;

	int mid = (lo + hi) / 2; // midpoint

	mergeSort(a, lo, mid);
	mergeSort(a, mid + 1, hi);
	merge(a, lo, mid, hi);
}

static void merge(int a[], int lo, int mid, int hi) {
	int nitems = hi - lo + 1;
	int *tmp = malloc(nitems * sizeof(int));

	int i = lo;
	int j = mid + 1;
	int k = 0;

	while (i <= mid && j <= hi) {
		if (a[i] <= a[j]) {
			tmp[k++] = a[i++];
		} else {
			tmp[k++] = a[j++];
		}
	}

	while (i <= mid) {
		tmp[k++] = a[i++];
	}
	while (j <= hi) {
		tmp[k++] = a[j++];
	}

	for (i = lo, k = 0; i <= hi; i++, k++) {
		a[i] = tmp[k];
	}

	free(tmp);
}
Vertical Spacing

Use vertical whitespace (blank lines between code) occasionally to indicate where the related parts of your code are.

There should always be at least one blank line between functions.

Within functions:

  • Not having a blank line between two lines of code indicates that the lines should be understood together, or are closely related, or are part of the same "step".
  • Using exactly one blank line indicates that two sections of code are distinct - they split your code into "paragraphs".
  • Using more than one blank line between two lines of code is mostly unnecessary, except when splitting a file into related sections.

The exact use of vertical whitespace is subjective, and your tutor will be able to give you feedback on your style throughout the term. Vertical whitespace is like chocolate -- some of it significantly improves your life, but too much can also be its own problem; and used in the wrong place it can be very confusing.

int main(void) {
	...

	mergeSort(arr, 0, size - 1);
}
static void mergeSort(int a[], int lo, int hi) {
	if (lo >= hi) return;
	int mid = (lo + hi) / 2; // midpoint
	mergeSort(a, lo, mid);
	mergeSort(a, mid + 1, hi);
	merge(a, lo, mid, hi);
}
static void merge(int a[], int lo, int mid, int hi) {
	int nitems = hi - lo + 1;

	int *tmp = malloc(nitems * sizeof(int));

	int i = lo;

	int j = mid + 1;

	int k = 0;

	while (i <= mid && j <= hi) {

		if (a[i] <= a[j]) {
	
			tmp[k++] = a[i++];
		
		} else {
	
			tmp[k++] = a[j++];
		
		}

	}
	while (i <= mid) {
		tmp[k++] = a[i++];
	}
	while (j <= hi) {
		tmp[k++] = a[j++];
	}
	for (i = lo, k = 0; i <= hi; i++, k++) {
		a[i] = tmp[k];
	}



	free(tmp);



}
Statements

Only one executable statement should be used per line of code.

Statements

Only one executable statement should be used per line of code.

Don't put multiple statements on a single line - it makes code very hard to read.

Statements

Only one executable statement should be used per line of code.

// BAD - the declarations should be on separate lines

int a = 1; int b = 2;
Line Width

Keep lines under 80 characters.

URLs in references are allowed to bypass this limit.

Line Width

Keep lines under 80 characters.

URLs in references are allowed to bypass this limit.

Break long lines up to keep them under 80 characters, unless keeping it as a longer line is significantly more readable.

Line Width

Keep lines under 80 characters.

URLs in references are allowed to bypass this limit.

// This is WAY too long:
if (something && somethingElse && somethingElseAgain && anotherThing && evenMoreThings && somethingElseEntirely && thereAreTooManyConditions && thisLineIsTooLong) {

// This is better (but consider using a function instead):
if (something && somethingElse && somethingElseAgain && anotherThing &&
        evenMoreThings && somethingElseEntirely &&
        thereAreTooManyConditions && thisLineIsTooLong) {

// This is best:
if (thoseThingsAreTrue(...)) {

Functions

Don't Repeat Yourself (DRY)

Do not copy-and-paste code. The same code should not appear twice in your program. Avoid repeating code by defining a function and calling it twice (or more).

Don't Repeat Yourself (DRY)

Do not copy-and-paste code. The same code should not appear twice in your program. Avoid repeating code by defining a function and calling it twice (or more).

Repeated code makes your program hard to read and hard to modify.

Function Length

Functions should be short and sweet, do just one thing, and do it well.

Functions should be at most 50 lines long, including comments and blank lines.

If you have a complex function that is too long, think about how you can (1) simplify the logic so that you can achieve the same thing with fewer lines, and/or (2) split the function into smaller helper functions and give them descriptive names.

Function Prototypes / Function Signatures

Function prototypes must be used for all functions (except main) and must come before all functions, appearing in the same order as the function implementations.

Function signatures must have the return type, function name, and state the argument list.

Function Prototypes / Function Signatures

Function prototypes must be used for all functions (except main) and must come before all functions, appearing in the same order as the function implementations.

Function signatures must have the return type, function name, and state the argument list.

Function prototypes describe functions by telling the compiler (and anyone reading the program):

  • The type of value the function returns
  • The name of the function
  • The type and name of any arguments to the function
Function Comments

Every ADT function must have a comment placed above its prototype in the header file describing the purpose of the function and any side-effects the function has.

Every other function must have a comment placed before the function implementation describing the purpose of the function and any side-effects the function has. Note: You do not need to do this for any functions (including stub functions) that are in the given starter code.

Function Comments

Every ADT function must have a comment placed above its prototype in the header file describing the purpose of the function and any side-effects the function has.

Every other function must have a comment placed before the function implementation describing the purpose of the function and any side-effects the function has. Note: You do not need to do this for any functions (including stub functions) that are in the given starter code.

// Returns a pointer to the last node in the list
// NULL is returned if the list is empty
struct node *last(struct node *head) {
Function Names

Function names should be descriptive, typically containing multiple words.

Names for most functions (exceptions below) should be in either snake_case or camelCase. Use only one of these approaches in your program. This should be consistent with your style for variable names.

When writing interface functions for ADTs, you should keep the naming consistent by including the name of the ADT at the beginning. Names of ADT interface functions should be in PascalCase. For example, SetNew, SetFree, SetAdd, SetContains, SetRemove.

Static Functions

Functions that are not intended to be called from other source files should be made static by including the static keyword before the return type.

Static Functions

Functions that are not intended to be called from other source files should be made static by including the static keyword before the return type.

Making a function static prevents its name from being visible outside the source file in which it is declared, thereby preventing it from being called (by name) in other source files.

One benefit of making a function static is that it will not conflict with another function of the same name in another source file. Therefore, you can have multiple functions with the same name in the same program, as long as they are in different source files. For example, suppose you are writing a program that uses both a linked list and a binary search tree. As long as these are implemented in different files, you can define a static function called newNode in each file.

Another benefit is that it clearly indicates to other programmers that the function should not and cannot be called from outside the source file, which can prevent mistakes. This is because the function may only work correctly if called in a specific way or in a specific context (e.g., by another function that performs some setup first), so if the function is called directly, it might not work correctly. Even though you won't need to work with other students in this course, this is still a good programming practice to follow.

Static Functions

Functions that are not intended to be called from other source files should be made static by including the static keyword before the return type.

static struct node *newNode(int value);
Function Order

Helper functions should be placed directly under the functions that call them.

Function Order

Helper functions should be placed directly under the functions that call them.

Ordering functions in this way reduces the scroll distance between a function its helper functions, which makes it easier for a reader to understand the interaction between a function and its helper functions, and hence the purpose of the helper functions.

This also means higher-level functions will be placed near the top of the file, which improves readability because a reader will usually be more interested in understanding the high-level details of a program first.

Function Order

Helper functions should be placed directly under the functions that call them.

int main(void) {
	a();
}

static void a(void) {
	b();
	c();
}

static void b(void) {
	d();
}

static void d(void) {
	...
}

static void c(void) {
	...
}
Function Order

Helper functions should be placed directly under the functions that call them.

static void c(void) {
	...
}

static void b(void) {
	d();
}

static void a(void) {
	b();
	c();
}

static void d(void) {
	...
}

int main(void) {
	a();
}
Function Arguments

Argument names in a function prototype should match the names used in the function implementation and should always be used in the prototype.

Long function argument lists may be broken over multiple lines.

Functions that do not take any arguments should use (void) instead of an empty argument list.

Function Arguments

Argument names in a function prototype should match the names used in the function implementation and should always be used in the prototype.

Long function argument lists may be broken over multiple lines.

Functions that do not take any arguments should use (void) instead of an empty argument list.

A space may optionally be used between the function name and argument list.

The return type, function name, and start of the argument list should all be on the same line.

If the argument list is too long to fit on a single line with the function name and return type, it may be split across multiple lines, with the additional lines being indented by 2 stops (8 spaces) or aligned with the first argument in the first line.

Function and argument naming is discussed in the functions section of this document.

Function Arguments

Argument names in a function prototype should match the names used in the function implementation and should always be used in the prototype.

Long function argument lists may be broken over multiple lines.

Functions that do not take any arguments should use (void) instead of an empty argument list.

// A short function
int aFunctionWithNoArguments(void);

// A short function
int aSimpleFunction(int argumentA, char *argumentB);

// A function with a lot of arguments
int aLongFunction(int argumentA, int argumentB, int argumentC,
                  int argumentD, int argumentE, int argumentF);

// Another function with a lot of arguments
int anotherLongFunction(int argumentA, int argumentB, int argumentC,
                        int argumentD, int argumentE, int argumentF);

// Another function with a lot of arguments.
// Note that the closing ');' is on a line by itself to make it clearer
// where the prototype ends.
int anotherLongFunction(
    int argumentA, int argumentB, int argumentC,
    int argumentD, int argumentE, int argumentF
);
Return

Do not overuse return statements.

Use multiple returns only if this makes your function less complex or more readable.

For example, it is acceptable to have an early return statement(s) to handle error conditions or special cases, as well as another return statement for the computation of the function's main result.

Comments

Comment Use

Where your code may be unclear to a reader, or require further explanation, you should leave brief comments describing the purpose of the code you have written.

Make sure that these comments describe the high-level purpose of the code, rather than simply stating what the code does at a low level. Comments should be used to describe, explain and/or justify code; they should not simply restate what the code is doing.

Comment Use

Where your code may be unclear to a reader, or require further explanation, you should leave brief comments describing the purpose of the code you have written.

Make sure that these comments describe the high-level purpose of the code, rather than simply stating what the code does at a low level. Comments should be used to describe, explain and/or justify code; they should not simply restate what the code is doing.

// If the tree is empty
if (tree == NULL) {
    ...
}
Comment Use

Where your code may be unclear to a reader, or require further explanation, you should leave brief comments describing the purpose of the code you have written.

Make sure that these comments describe the high-level purpose of the code, rather than simply stating what the code does at a low level. Comments should be used to describe, explain and/or justify code; they should not simply restate what the code is doing.

// Do NOT do this

// If the given pointer points to NULL
if (tree == NULL) {
    ...
}

// Declare a file pointer
FILE *file;

// Initialise sum as 0
int sum = 0;

// Initialise wordList as NULL
List wordList = NULL;

// Increment count
count++;

// Declare word name variable of max 100 characters
char word[MAX_WORD];

// Return a pointer to the new node
return new;
Comment Agreement

Comments should be accurate and agree with the code they describe. If you modify some existing code, you should ensure that the surrounding comments are still accurate.

Trailing Comments

Do not use trailing comments (i.e., comments which appear at the end of a line containing other code) unless the comment is very short (i.e., a few words at most) and describes only the code that it appears next to.

Constants

Constants

Use #defines to give constants names.

enums may be used to give names to groups of related constants.

#define and enum names must be written in ALL_CAPS_WITH_UNDERSCORES.

Constants

Use #defines to give constants names.

enums may be used to give names to groups of related constants.

#define and enum names must be written in ALL_CAPS_WITH_UNDERSCORES.

Unexplained numbers, often called magic numbers, can be hard to understand.

If a number appears multiple times in the code, bugs are created when the is code changed but not all occurrences of the number are changed.

A similar problem is that a number may appear in the code for multiple reasons, e.g., a commonly used number like 10, and if the code needs to be changed it can be hard to determine which occurrences of the number should be changed.

enum is also used by programmers to give constants names in C.

Constants

Use #defines to give constants names.

enums may be used to give names to groups of related constants.

#define and enum names must be written in ALL_CAPS_WITH_UNDERSCORES.

#define DAYS_OF_WEEK 7

typedef enum {
	MONDAY,
	TUESDAY,
	WEDNESDAY,
	THURSDAY,
	FRIDAY,
	SATURDAY,
	SUNDAY,
} Weekday;

// ....

int array[DAYS_OF_WEEK];
int i = 0;
while (i < DAYS_OF_WEEK) {
    a[i] = i;
    i++;
}
Constants

Use #defines to give constants names.

enums may be used to give names to groups of related constants.

#define and enum names must be written in ALL_CAPS_WITH_UNDERSCORES.

// BAD - the constant 7 should be given a name
int array[7];
int i = 0;
while (i < 7) {
    a[i] = i;
    i++;
}

Variables

Variable Names

Descriptive variable names should always be used where possible.

Short variable names such as x or i are acceptable if there is no appropriate long descriptive name (which is often the case for variables used as loop counters), the meaning of the variable is obvious, or if the variable is used in a very small scope. Examples of acceptable short variable names are:

  • s for string
  • l for list
  • t for tree
  • g for graph
  • n for node
  • v for vertex or value
  • u, v and w for vertices
  • i, j and k for loop counters

Variable names must begin with a lower case letter.

Multi-word variable names be in either snake_case or camelCase. Use only one of these approaches in your program. This should be consistent with your style for function names.

Declaring Variables

Variables should be declared when (or close to when) they are first assigned a value, and close to when they are first used.

Do not use the same variable name for multiple purposes.

Do not declare more than one variable on the same line.

Declaring Variables

Variables should be declared when (or close to when) they are first assigned a value, and close to when they are first used.

Do not use the same variable name for multiple purposes.

Do not declare more than one variable on the same line.

Assigning a value to a variable when it is used prevents confusion. Declaring it close to the place it is first used makes it easy to find and understand; and difficult to accidentally reassign elsewhere.

If you need a new variable, give it a different name. Reusing variable names leads to subtle and hard to find bugs. Declaring multiple variables on the same line can have subtle behaviour when declaring pointers, and can cause harder to read code.

Declaring Variables

Variables should be declared when (or close to when) they are first assigned a value, and close to when they are first used.

Do not use the same variable name for multiple purposes.

Do not declare more than one variable on the same line.

int i = 0;

int j;
if (scanf("%d", &j) == 1) {
    // do something
}
Declaring Variables

Variables should be declared when (or close to when) they are first assigned a value, and close to when they are first used.

Do not use the same variable name for multiple purposes.

Do not declare more than one variable on the same line.

int i;

// a lot of other code not involving i

i = 0;
Numeric Variables

Use only the types int and double for numeric variables.

You should not need to use other types such as short and float in this course.

Numeric Variables

Use only the types int and double for numeric variables.

You should not need to use other types such as short and float in this course.

int age = 18;
double height = 1.65;
Numeric Variables

Use only the types int and double for numeric variables.

You should not need to use other types such as short and float in this course.

short age = 18;
float height = 1.65;

// common C programming bug - getchar returns an int
char c = getchar();
Loop Variables

It is recommended variables controlling while loops be declared before the loop and used only to control that loop.

It is recommended variables controlling for loops be declared as part of the for loop.

Loop Variables

It is recommended variables controlling while loops be declared before the loop and used only to control that loop.

It is recommended variables controlling for loops be declared as part of the for loop.

int i = 0;
while (i < MAXIMUM) {
    // do something
    i++;
}

for (int i = 0; i < MAXIMUM; i++) {
    // do something
}
Global and Static Variables

Global variables and static variables should not be used unless you are explicitly permitted.

There is likely to be a major style deduction if you use global variables or static variables without permission.

Global and Static Variables

Global variables and static variables should not be used unless you are explicitly permitted.

There is likely to be a major style deduction if you use global variables or static variables without permission.

Global variables are variables that are declared outside of a function.

Global variables make code hard to read, maintain and parallelize.

There are important uses for global variables but not in this course.

The only acceptable use likely in this course would be a global variable to control debug output.

All the above also applies to variables inside functions which have the static qualifier.

The values in static variables persist across multiple calls to the function creating similar problems.

Variable-Length Arrays

Variable-length arrays should not be used in this course.

In this course, code using variable-length arrays will not compile.

Variable-Length Arrays

Variable-length arrays should not be used in this course.

In this course, code using variable-length arrays will not compile.

Variable-length arrays are arrays whose size is not known when the program compiles.

Arrays should only be declared with a constant size, such as with a literal integer, e.g. int array[12], or with a constant, e.g. int array[MAX_LENGTH]. Arrays should not be declared with a size using a variable, such as int array[x].

Many C implementations poorly handle local variables consuming more than a small amount of memory, often terminating with a cryptic message if the storage used for local variables exceeds a few megabytes.

This applies to both arrays with a fixed size at compile-time and variable-length arrays.

For this reason, programmers often use malloc for larger arrays (10000+ elements). malloc gives the programmer complete control over storage lifetime, but as a consequence introduces other classes of bugs.

Libraries

Libraries

Unless a task specifies otherwise, you are not permitted to #include additional libraries, as the task likely already #includes all the libraries necessary to solve the task. If you believe this is not the case, you should ask on the course forum.

External Programs

Unless a task specifies otherwise, you are not permitted to run other programs from your C program, using the function system or otherwise.

Use These C Features Sensibly

break

Use break statements sensibly.

break

Use break statements sensibly.

Novice programmers often over-use break statements, producing confusing and difficult to debug code.

Use break only when you are sure that it makes your code simpler and more readable.

continue

Use continue statements sensibly.

continue

Use continue statements sensibly.

Novice programmers often misuse and over-use continue statements, producing confusing and difficult to debug code.

It is best not to use continue until you are a more experienced programmer - and then use it carefully ensuring it makes your code more readable.

switch

Use switch statements sensibly.

switch

Use switch statements sensibly.

The C switch statement can have subtle and confusing behaviour.

When using the switch statement, use break statements to avoid fall-through behaviour, unless fall-through is intended.

Ternary if

Use the ternary if construct sensibly.

Ternary if

Use the ternary if construct sensibly.

Unlike some other languages, C is a statement-oriented language; this means, for example, an if statement doesn't have an inherent value and can't be used inside an expression.

C does have an operator similar to the if statement, the ternary if, which can be used in expressions. The ternary if construct is:

(condition) ? true_expression : false_expression

Novice programmers attempting to use the ternary if typically construct unreadable code and confuse themselves.

Most experienced programmers either avoid the ternary if entirely, or use it in very limited circumstances.

do ... while

Use the do { ... } while (condition); construct sensibly.

do ... while

Use the do { ... } while (condition); construct sensibly.

The do { ... } while (condition); construct is similar to a normal while loop, except the body will always run once before the condition is checked.

Novice programmers often confuse themselves and construct hard-to-debug code using do...while loops.

Many experienced programmers avoid do...while entirely, but they are commonly used in some contexts such as kernel programming by experienced programmers.

Avoid These C Features

Macros

Avoid #define statements with parameters (usually called macros) unless you are experienced with them.

Define a function instead.

Macros

Avoid #define statements with parameters (usually called macros) unless you are experienced with them.

Define a function instead.

Macros introduce a nasty class of bugs due to their implementation as textual pre-processing.

Macros were primarily used for efficiency in the past. This is no longer needed.

Modern C compilers will produce the same code for functions as for macros, without the risk of nasty bugs.

Pointer Arithmetic

Avoid pointer arithmetic. Use array indices instead.

Pointer Arithmetic

Avoid pointer arithmetic. Use array indices instead.

Experienced programmers use pointer arithmetic to produce succinct idiomatic code.

Novice programmers often confuse themselves by trying to use pointer arithmetic. Any code using pointer arithmetic can also be written using array indices. Use array indices until you are an experienced programmer.

Type Cast

Avoid type casts, except when converting between numeric types such as from int to double or when using standard library functions such as bsearch or qsort where a cast from void * is required.

Type Cast

Avoid type casts, except when converting between numeric types such as from int to double or when using standard library functions such as bsearch or qsort where a cast from void * is required.

Safe use of type casts, other than between numeric types, involves a deep knowledge of C semantics.

A need for other uses for type casts should not arise in this course, and other uses for type casts should be avoided.

Type Cast

Avoid type casts, except when converting between numeric types such as from int to double or when using standard library functions such as bsearch or qsort where a cast from void * is required.

int x = 42;
int y = 11;
double f;
f = x / ((double) y); // convert y to a double
Goto

Do not use goto.

There will be a major style deduction if you use goto.

Goto

Do not use goto.

There will be a major style deduction if you use goto.

Most programmers completely avoid use of the goto statement because it can produce incomprehensible code.

Introductory programming courses always ban use of goto to help beginner programmers learn better coding techniques.

There are very limited circumstances, e.g., writing device drivers or emulators, where goto's use is appropriate, but such circumstances will not arise in this course.

Comma Operator

Do not use the comma operator to combine multiple expressions, except in the increment of a for loop.

Comma Operator

Do not use the comma operator to combine multiple expressions, except in the increment of a for loop.

Novice programmers attempting to use the comma operator typically construct unreadable code and confuse themselves.

union

Do not use unions; use structs instead.

union

Do not use unions; use structs instead.

Unions allow a polymorphic datatype to occupy less space than if a struct was used. They are sometimes useful for embedded programming and for building language implementations.

No need for unions should arise in this course.