Style Guide
You should take time to read this file carefully whenever new syntax is introduced in lectures. 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.
Contents
File Layout
The layout of your .c files should look similar to the template below.
Header Comment
All files must have a header comment of the with a description, the names and zIDs of all students who worked on it, the name of the tutor, the tutorial code, and the date the file was created in the format
YYYY-MM-DD
.
Header Comment
All files must have a header comment of the with a description, the names and zIDs of all students who worked on it, the name of the tutor, the tutorial code, and the date the file was created in the format
YYYY-MM-DD
.
All files will start with a header comment. This is just a comment that contains:
- A description of what the file is for or what the program does,
- The names and zIDs of everyone who worked on the file,
- Your tutor’s name, and
- The date that the file was made, in the form
YYYY-MM-DD
.
Suppose the student Julian Saknussemm was working on a program that simulated an
ant farm, the header file for the antFarm.c
file would look something like the
following.
Header Comment
All files must have a header comment of the with a description, the names and zIDs of all students who worked on it, the name of the tutor, the tutorial code, and the date the file was created in the format
YYYY-MM-DD
.
Includes
#include
s should be directly after the header comment.
Includes
#include
s should be directly after the header comment.
#include
s are how we use code from other files in our .c files so that we
can share functions between files. #include
s always come after the header
comment.
Include Order
System includes should come before local includes and all includes should be in alphabetical order.
Include Order
System includes should come before local includes and all includes should be in alphabetical order.
System includes, which use <
and >
and look like #include <stdlib.h>
,
are used to include header files that are installed on the operating system.
These should be in alphabetical order and should come first.
Local includes, which use "
and look like #include "myModule.h"
, are used
to include our own header files. These should also be in alphabetical order and
should come after system includes.
Include Order
System includes should come before local includes and all includes should be in alphabetical order.
Constants
#define
s should always come after any#include
s and may be used in all files. Constant names should always be in snake case with upper-case letters.
Constants
#define
s should always come after any#include
s and may be used in all files. Constant names should always be in snake case with upper-case letters.
Constants are created using #define
and allow us to create constant values
with a name. We give these values a name near the start of our file and then we
can use the name in the rest of the file. This means that if we want to change
the value at any point, we only need to change it once.
If we want to use any mathematical operators in our constants, we
need to surround everything with parentheses. #define
can also be used to make
constants with float
s, char
s, and strings.
Constants
#define
s should always come after any#include
s and may be used in all files. Constant names should always be in snake case with upper-case letters.
Main Function
The
main
function should have the signatureint main(void) {}
or once we get further into the course and use command line arguments
int main(int argc, char *argv[]) {}
It should return
0
orEXIT_SUCCESS
on a successful exit. Themain
function should be the first function in a file where it is used and must follow all function prototypes.
Main Function
The
main
function should have the signatureint main(void) {}
or once we get further into the course and use command line arguments
int main(int argc, char *argv[]) {}
It should return
0
orEXIT_SUCCESS
on a successful exit. Themain
function should be the first function in a file where it is used and must follow all function prototypes.
Programs tend to be written in a top-down manner.
This means we start at the top,
at the highest level of abstraction,
and progressively descend through the levels.
Usually, the main
function is
the highest level of abstraction:
the place where the program commences.
When we compile and link a program,
the main
function is assumed to be
the place where that program’s execution starts.
By convention, it takes two parameters:
the count of command-line arguments, argc
,
and the length of the command-line argument vector, argv
.
These names, argc
and argv
,
are conventions followed by most C programmers.
Main Function
The
main
function should have the signatureint main(int argc, char *argv[]) {}
and should returnEXIT_SUCCESS
on a successful exit. Themain
function should be the first function in a file where it is used and must follow all function prototypes.
Code Structure
Statements
Only one executable statement should be used per line of code.
Line Width
No line should be more than 72 characters wide.
Line Width
No line should be more than 72 characters wide.
A general rule with file structure is that no line should be more than 72 characters wide. This means we will be able to read it in most text editors more easily.
Indentation
Between any pair of braces, the indentation level should increase by 4 spaces. 4 spaces should be used for all indentation.
Variable Names
Variables should be named in camel case, starting with a lower-case letter. Camel Case is the practice of writing compound words so that apart from the first word, each subsequent word begins with a capital letter, with no intervening spaces or punctuation. Eg minimumAge
Descriptive variable names should always be used. Single character variable names may be used for arbitrary integers in mathematical functions, for example
int multiply(int x, int y)
, or for loop counters.
Compact Braces and Indentation
Functions, loops, and ifs should always use braces and should should use compact braces.
Compact Braces and Indentation
Functions, loops, and ifs should always use braces and should 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
.
Compact Braces and Indentation
Functions, loops, and ifs should always use braces and should should use compact braces.
Arrays
Arrays should only be declared with a constant size, such as with a literal integer, such as
int numArray[12]
or with a constant, such aschar *name[MAX_LENGTH]
. Arrays should not be declared with a size using a variable, such asint numArray[x]
.
Loops
In
while
loops, any counter variables or reference pointers should be declared immediately before the loop. There should also be an incrementor as the last line of the loop. Braces should be in compact style, in a similar manner to function braces and must always be used.
Loops
In
while
loops, any counter variables or reference pointers should be declared immediately before the loop. There should also be an incrementor as the last line of the loop. Braces should be in compact style, in a similar manner to function braces and must always be used.
Global and Static Variables
Neither global variables nor static variables should be used.
Global and Static Variables
Neither global variables nor static variables should be used.
Variables tend to belong to the area of code that declares them, an effect known as scope. In C, it’s valid to have variables declared in the global scope. However, this is considered bad practice and should be avoided, as values in global variables may cause unintended effects in functions.
The static
qualifier
has a very different effect on variables
to the effect it has on functions.
Values in static
variables
persist across multiple calls to the function,
which is even more insidious than global variables
for introducing unintended effects.
Comma Operator
The comma operator should not be used.
Goto
goto
should not be used.
Goto
goto
should not be used.
C allows us to jump to arbitrary locations in our program. You can see why that might be a terrible idea: there’s no guarantee that one line of code would follow another.
Ternary
Ternary syntax
(condition) ? true_expression : false_expression
should not be used.
Ternary
Ternary syntax
(condition) ? true_expression : false_expression
should not be used.
Unlike some other languages,
C is a statement-oriented language,
so an if
statement doesn’t have a value.
A ternary allows us to produce different values
depending on a particular condition.
Ternaries are a very, very easy way
to produce really confusing effects,
and you shouldn’t use them.
Break and Continue
break
orcontinue
should not be used in loops.
Break and Continue
break
orcontinue
should not be used in loops.
The break
and continue
keywords
can change the logical flow of control
in loop statements,
with the effect that
the body of a loop
may not proceed from top to bottom
under all circumstances.
It can be confusing and bizarre,
and under all circumstances
(that you encounter in COMP1511)
you should reconsider your logic instead.
Do While
do {} while (condition);
should not be used; usewhile (condition) {}
instead.
Do While
do {} while (condition);
should not be used; 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.
It’s very easy,
to construct do...while
expressions
that are confusing and poorly structured,
and in COMP1911 we prefer to avoid them.
Functions
Function Purpose
Functions should have one clearly defined purpose and should be short.
Function Purpose
Functions should have one clearly defined purpose and should be short.
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 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 start the argument list on the same line.
Function Prototypes
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 start the argument list on the same line.
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.
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.
Const Arguments
const
arguments and types should not be used.
Pointers
Pointer Arithmetic
Pointer arithmetic should not be used.
Pointer Arithmetic
Pointer arithmetic should not be used.
Pointer arithmetic is one of the stranger features of C. Pointers are, of course, numeric values, so in theory, you can manipulate them as you would any other number. In practice, though, changing pointers yourself is a bad idea, and is likely to cause serious problems.
One of the more common places
we often see pointer arithmetic
is string manipulation:
a lazy programmer might say *string++
,
to get the current character in the array string
,
while also moving it along to the next value.
This changes string
, of course,
meaning you lose the beginning of the string.
Indirectly, the ++
is pointer arithmetic:
it’s the addition of one to the value.
This is, in and of itself, fairly harmless,
but ++
or +1
may not have the effect you expect
if a value would take up multiple bytes.
Instead, it would step along by the size,
effectively emulating the behaviour of []
but being more confusing.
You should always access array elements
using index notation, []
.
Function Pointers
Function pointers should not be used.
Function Pointers
Function pointers should not be used.
You can point at literally everything in C, even the functions you write. Function pointers are fairly dangerous, as they make it very easy to cause your program execute arbitrary code that may not be the code you expected. For the security implications alone, they’re banned.
Another, more subtle trick is the use of function pointers as a poor-man’s generic programming; that’s not ever needed in this course, so we can safely ban it.
Structs
Struct Order
struct
and type definitions should always follow constant definitions, come before function prototypes, and structs should only be found in .c files.
Struct Order
struct
and type definitions should always follow constant definitions, come before function prototypes, and structs should only be found in .c files.
We can create structured data using struct
s, which help us define a
collection of related pieces of data. These allow us to contain several
different values of different type in one value that can be passed around. These
should always be defined after constants.
Struct naming
struct
s should always have an associatedtypedef
. Thestruct
tag should be named in camel-case starting with , a lower-case letter. The type name should be the same as the struct tag but with the first letter in capitals.
Struct naming
struct
s should always have an associatedtypedef
. Thestruct
tag should be named in camel-case starting with a lower-case letter. The type name should be the same as the struct tag but with the first letter in capitals.
The names of struct
tags must be in camel case starting with a lower-case letter.
A struct
should always have an associated typedef
so that it can be
used more cleanly as a type. The type name should be the same as the struct
tag, but with the first letter in capitals.
Struct naming
struct
s should always have an associatedtypedef
. Thestruct
tag should be named in camel-case starting with a lower-case letter. The type name should be the same as the struct tag but with the first letter in capitals.
Unions
union
s should not be used.
Multiple Files
Program File
File names with a
main
function must be named in camel case starting with a lower-case letter, likemyProgram.c
.
Program File
File names with a
main
function must be named in camel case starting with a lower-case letter, likemyProgram.c
.
Files that contain a main
function, should be named using camel case, starting
with a lower-case letter. These should be compiled into programs of the same
name, but without the .c suffix. For example, the file exampleProgram.c
that
contains the function main
, should be compiled into the program
exampleProgram
(using dcc -o exampleProgram exampleProgram.c
).
Module File
Module files must have the same name for the .h and .c file, and the name must be in camel case starting in a lower-case letter, like
myModule.c
andmyModule.h
.
Module File
Module files must have the same name for the .h and .c file, and the name must be in camel case starting in a lower-case letter, like
myModule.c
andmyModule.h
.
Later in the course, we will look at breaking programs into multiple files. We will be breaking programs into .h (header files) and multiple .c (source files).
These files will come in pairs, the header file will describe the functions and
types used in the source file. To show that they are related, they will both
have identical names, however the header file will end in .h
and the source
file will end in .c.
If the pair of files provides a set of useful functions to do related work
(which we would call a module), then the names of the files will be in camel
case, starting in a lower-case character. For example, we may have the module
usefulModuleName
, it would be split into usefulModuleName.h
and
usefulModuleName.c
.
Header Guard
Header guards must be used in all .h files, and must have a similar name to the header file itself using snake case with upper-case letters and the
_H
suffix. The closing#endif
guard must have a comment with the constant name.
Header Guard
Header guards must be used in all .h files, and must have a similar name to the header file itself using snake case with upper-case letters and the
_H
suffix. The closing#endif
guard must have a comment with the constant name.
Header guards tell the compiler that we only want to use the code in a header
once. This means that if we somehow #include
the header multiple times, the
code only gets used once. We need to do this because we aren’t allowed to define
the same function or type multiple times; if we try to, the compiler will
generate error messages.
Thankfully, header guards are easy. After the header comment in a .h file, you add the opening header guard and at the very end of your .h file, you add the closing header guard.
The #ifndef
tells the compiler to use the code until the #endif
only if the
constant has not been #define
d. This will only be true the first time the file is
included as we then #define
the constant the first time the code is used.
Header Guard
Header guards must be used in all .h files, and must have a similar name to the header file itself using snake case with upper-case letters and the
_H
suffix. The closing#endif
guard must have a comment with the constant name.
Example
Here is an example of how your code should look:
Security
It can be very easy to write code with security vulnerabilities in C, however careful design and good coding habits can help prevent many of these vulnerabilities.
Integer Overflows
Code should not contain integer overflows.
Integer Overflows
Code should not contain integer overflows.
Integer overflows are vulnerabilities where an integer can be incremented or
decremented that it wraps around. This can be used to have adverse affects in a
number of ways. dcc
compiles your code to catch this error, so you don’t need
to worry as much.
Buffer Overflows
Code should not contain buffer overflows.
Buffer Overflows
Code should not contain buffer overflows.
The one vulnerability that you need to look out for is buffer overflows as we cannot protect you from these so easily. A buffer overflow is where data being read into an array (usually as a string) can use up more space than the array contains and start writing into other parts of memory. If a user can control this, then they may be able to run code from within your program.
The best things to keep in mind when dealing with arrays are:
- Am I sure that data can’t be written past the end of the array?
- Am I sure that the last byte in the array will always be a terminating
character (
'\0'
) or unused? - Can I ensure that there is a limit on how many bytes of characters are read.
Buffer Overflows
Code should not contain buffer overflows.
Format String Vulnerabilities
Code should not contain
printf
format string vulnerabilities.
Format String Vulnerabilities
Code should not contain
printf
format string vulnerabilities.
The printf
function uses a string as its first argument to tell it how to
format the remaining variables passed into the function. If a user can control
what string is used for a printf
statement, they not only have the ability to
read all of the data in memory, but due to a quirk in the way printf
can be
used, they can also run their own code from within your program. Basically, you
should never have a printf
statement with a variable as the first argument.
The compiler will warn you if your code contains printf
vulnerabilities.
Format String Vulnerabilities
Code should not contain
printf
format string vulnerabilities.