COMP1521 Style Guide
Contents
You should come back and read this as you learn new syntax and concepts in lectures, so don't worry if you don't understand everything here yet.
Permitted Language Features
Language Features
If you do not have much coding experience, it is strongly recommended you only use C language features that have been described in lectures. You will find it much easier to follow the same collective path as the rest of the class.
If you have significant programming experience you are in general free to use language features you have learned outside this course, unless it is explicitly forbidden below. But please bear in mind you may not be able to get assistance from tutors, or in the course forum with your code.
Recommended Code Layout
A number of approaches to formatting C code are popular among experienced programmers.
If you have significant programming experience and have learnt one of the C coding styles used bu good programmers you are free to use this style in this course.
If you do not have much coding experience, it is strongly recommended you follow exactly the same layout as lecture, tut and lab examples.
The layout is described below.
Header Comment
All program should have a header comment.
For small lab exercises 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 program should have a header comment.
For small lab exercises 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.
// // COMP1511 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 <stdio.h> #include <string.h> #include <math.h> #define MEANING_OF_LIFE 42 // function prototypes would go here int main(void) { printf("Hello!\n"); return 0; } // function definitions would go here
Braces and Indentation
Functions, loops, and ifs should always use braces and should use compact braces.
Braces and Indentation
Functions, loops, and ifs should always use braces and should use compact braces.
For functions, the opening brace ({
)
should be on the same line
as the function prototype.
The closing brace (}
)
should be on its own line
and not indented.
For if
, the opening brace ({
)
should be in the same line
as if
and the loop condition.
The closing brace
should be on its own line
and should line up
with the i
in the matching if
.
For else
and else if
,
the else
should be on the same line
as the closing brace (}
)
before it.
The opening brace ({
)
should be on the same line
as the else
, or the else if
and the condition.
The closing brace (}
)
should be on its own line
and should line up
with the closing brace (}
) before it.
For loops, the opening brace ({
)
should be on the same line
as while
and the loop condition.
The closing brace (}
)
should be on its own line
and should line up
with the w
in the matching while
.
Braces and Indentation
Functions, loops, and ifs should always use braces and should use compact braces.
int someFunction(int a, int b) { if (a > b) { // do something } else if (b < a) { // do something } else { // do something } int i = 0; while (i < 0) { // do something i++; } }
Braces and Indentation
Functions, loops, and ifs should always use braces and should use compact braces.
// BAD - the code between the {} should be indented if (i % 2 == 0) { printf("even"); } else { printf("odd"); } // BAD - put newlines after { and after } while (i < 0) { i++; } // BAD - always use braces on ifs and whiles while (i < 0) i++; // BAD - closing braces don't line up if (a > b) { // do something } else if (b < a) { // do something } else { // do something }
Indentation
Between any pair of braces, the indentation level should increase by 4 spaces. Spaces should be used for all indentation.
Spaces
Use a space after keywords such as:
if, while, for, returnUse a space on each side of binary operators such as
= + - < > * / % <= >= == !=Do not use space after prefix unary operators such as
& * !Do not use space before postfix unary operators such as
++ --Do not use space on each side of member access operators such as. ->
Spaces
Use a space after keywords such as:
if, while, for, returnUse a space on each side of binary operators such as
= + - < > * / % <= >= == !=Do not use space after prefix unary operators such as
& * !Do not use space before postfix unary operators such as
++ --Do not use space on each side of member access operators such as. ->
// This is correct: if (a + b > c) { i++; curr = curr->next; } // This is *not* correct: if(a+b>c) { i ++; curr = curr -> next; }
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.
Line Width
Keep lines under 80 characters.
Line Width
Keep lines under 80 characters.
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.
// 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()) {
Constants
Constants
Use
#define
to give constants names.
#defines
must be written inALL_CAPS_WITH_UNDERSCORES
.
Constants
Use
#define
to give constants names.
#defines
must be written inALL_CAPS_WITH_UNDERSCORES
.
Unexplained numbers,often called magic numbers, appearing in code make it hard to understand.
If a number appears multiple times in the code, bugs are created when the 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 be programmers to give constants names in C.
Constants
Use
#define
to give constants names.
#defines
must be written inALL_CAPS_WITH_UNDERSCORES
.
#define DAYS_OF_WEEK 7 // .... int array[DAYS_OF_WEEK]; int i = 0; while (i < DAYS_OF_WEEK) { a[i] = i; i++; }
Constants
Use
#define
to give constants names.
#defines
must be written inALL_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
ori
are acceptable if there is no appropriate long descriptive name. This is often the case for variables used as 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.
#define
names must be inALL_CAPS_WITH_UNDERSCORES
.
Declaring Variables
Variables can be declared when they are first assigned a value, as part of assigning the value.
Declaring Variables
Variables can be declared when they are first assigned a value, as part of assigning the value.
// This is fine: int i = 0; // Probably avoid this: int i; [insert a lot of other code here] i = 0;
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 should be avoided. Static variables should be avoided.
Global and Static Variables
Global variables should be avoided. Static variables should be avoided.
Global variables are variables that are declared outside of a function.
Global variables make code hard to read, debug, maintain and parallelize.
There are important uses for global variables but few if any exercises in this course will require them.
There is likely to be a major style deduction if you use global variables unnecessarily.
const global variables are an exception. As they are read-only they do not have the disadvantages described here.
Static variables inside functions persist between functions calls which also makes code hard to debug and parallelize.
There is likely to be a major style deduction if you use static variables unnecessarily.
Large Arrays As Local Variables
Large arrays should not be declared as local variables.
Large Arrays As Local Variables
Large arrays should not be declared as local variables.
Many C implementations handle poorly 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 both to arrays with size fixed at compile-time and variable-length arrays.
For this reason programmers often use malloc for larger arrays. 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 only permitted to call functions from the default C libraries.
In other words, you are not permitted to use the
-l
option to dcc/gcc/clang to include other libraries.
External Programs
Unless a task specifies otherwise, you are not permitted to run other programs from your C program, using the function
posix_spawn
,system
,popen
,fork
,exec
or otherwise.Some tasks will require use of these functions. Most tasks will require a single C (or MIPS) program.
Warnings, Errors & Undefined Behaviour
Compile-time Warnings
Your code should not generate any warnings or errors when compiled with dcc.
You should assume warnings from dcc are errors that need to be fixed.
Invalid C, Run-time errors & Undefined Behaviour
Your code should not perform any operation which results in undefined behaviour or a runtime error (when given any input that is permitted for the exercise).
For example, you must ensure your code does not access uninitialized variables, overflow numeric variables, access non-existent illegal array elements or dereference NULL pointers.
All of these will heavily penalized in marking.
We will use some code with undefined or implementation-specific behaviour to explore data representation in lectures and labs. You will not be asked to write such code.
Invalid C, Run-time errors & Undefined Behaviour
Your code should not perform any operation which results in undefined behaviour or a runtime error (when given any input that is permitted for the exercise).
For example, you must ensure your code does not access uninitialized variables, overflow numeric variables, access non-existent illegal array elements or dereference NULL pointers.
All of these will heavily penalized in marking.
We will use some code with undefined or implementation-specific behaviour to explore data representation in lectures and labs. You will not be asked to write such code.
Novice programmers need to be very careful that their code is not invalid.
For example, they need to carefully consider whether an expression used to index an array will always result in a valid array index.
Invalid C is a frequent source of serious security exploits - for example attackers often exploit invalid array indices and integer overflows.
Functions
Repeated Code
Do not cut-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
Do not cut-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 understand and hard to change.
Function Purpose
Functions should have one clearly defined purpose and should be short.
"Functions should do one thing, and one thing only."
Function Purpose
Functions should have one clearly defined purpose and should be short.
"Functions should do one thing, and one thing only."
If you have a function that performs two smaller tasks in sequence, make a function for each of those smaller tasks and call them from the more abstract function. Aim for your functions to be less than 20 lines or less. If they are long, think of how you can break them up into smaller functions.
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 be 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 function must have a comment placed before the function implementation describing the purpose of the function and any side-effects the function has.
Function Comments
Every function must have a comment placed before the function implementation describing the purpose of the function and any side-effects the function has.
// return pointer to last node in list // NULL is returned if list is empty struct node *last(struct node *head) {
Function Names
Function names should be descriptive, typically containing multiple words. Function names containing multiple words should be in either snake_case or camelCase. Do not use both approaches in the one program.
Function Names
Function names should be descriptive, typically containing multiple words. Function names containing multiple words should be in either snake_case or camelCase. Do not use both approaches in the one program.
When writing functions for ADTs, you may want to keep your naming consistent and the abstract type name will usually appear in the function name, usually at the end.
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 and the function name.
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
Minimise the number of return statements in your functions.
Have multiple 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.
Avoid These C Features
break
Minimize use of
break
statements.
break
Minimize use of
break
statements.
Novice programmers often over-use break
statements.
producing confusing and difficult to debug code.
Use break
only when you are sure it makes your
code simpler and more readable.
continue
Avoid using the
continue
statement.
continue
Avoid using the
continue
statement.
Novice programmers often misuse and over-use continue
statements,
producing confusing and difficult to debug code.
Do not to use continue
unless you are confident in your programming abilitites
and sure it makes your code simpler and more readable.
Macros
Avoid using
#define
statements with parameters (usually called macros).Where possible, define a function instead.
Macros
Avoid using
#define
statements with parameters (usually called macros).Where possible, 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 generally produce the same code for functions as for macros, without the risk of nasty bugs.
Exceptions include the use of macros for (meta) purposes which can not be implemented with functions,
or to pass arguments to variadic functions such as the printf
family of functions.
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 confuse themselves by trying to use pointer arithmetic. Any code using pointer arithmetic can also be written using array indices. Use array indices unless you are confident in your programming ability and are sure it is produces more readable code then array indices.
Type Cast
Avoid type casts, except converting between numeric types such
uint32_t
,int
anddouble
.
Type Cast
Avoid type casts, except converting between numeric types such
uint32_t
,int
anddouble
.
Safe use of type casts, other than between numeric types, involves a deep knowledge of C semantics.
An exception would be implementing your own collections data types, for a challenge exercise or the part of an assignment.
We also use type casts to explore data representation in lectures and labs. You will not be asked to write such code.
Type Cast
Avoid type casts, except converting between numeric types such
uint32_t
,int
anddouble
.
int x = 42; int y = 11; double f; f = x/((double)y); // convert y to a double
Switch
Avoid the
switch
statement.
Switch
Avoid the
switch
statement.
The C switch
statement can have subtle and confusing behaviour.
While there are some circumstances, e.g. writing emulators where its use is desirable, in general, leave avoid its use until you are a more experienced programmer.
Use if
...else if
...else
unless you are sure using switch
makes your code more readable..
Goto
Do not use
goto
.
Goto
Do not 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 limited circumstances, e.g. writing device drivers or emulators, where goto's use is appropriate
There is likely to be a major style deduction if you use goto
.
An exception is where we deliberately write C code which can be directly mapped to assembler.
Comma Operator
Avoid using the comma operator to combine multiple expressions.
Comma Operator
Avoid using the comma operator to combine multiple expressions.
Novice programmers attempting to use the comma operator typically construct unreadable code and confuse themselves.
Experienced programmer usually avoid comma completely.
Ternary If
Avoid using the ternary if:
(condition) ? true_expression : false_expression
. It should be used only if it makes you code simpler and more readable.
Ternary If
Avoid using the ternary if:
(condition) ? true_expression : false_expression
. It should be used only if it makes you code simpler and more readable.
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.
Novice programmers attempting to use the ternary if typically construct unreadable code and confuse themselves.
Many experienced programmers avoid the ternary if
entirely,
Use a ternary if only if you are confident in your programming ability and are sure it is produces simpler more readable code.
do
...while
Avoid using
do {} while (condition);
usewhile (condition) {}
instead.
do
...while
Avoid using
do {} while (condition);
usewhile (condition) {}
instead.
The do { ... } while (condition);
construct
is similar to a normal while (condition) { ... }
,
except the body will always run
before a 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.
union
Do not use
union
s usestruct
s instead.
union
Do not use
union
s usestruct
s instead.
Unions allow a polymorphic datatype to occupy less space than if a struct
was used.
Union introduce a nasty class of bugs which are hard to debug and best avoided by novice programmers.
Unions are also sometimes useful for embedded programming and for building language implementations.
We also use unions to explore data representation in lectures and labs. You will not be asked to write such code.