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
Syntax
Syntax and Libraries
You should not use any syntax or library until it has been covered in lectures.
File Layout
The layout of your .c files should look similar to the template below. You can download a copy of the template and save it to your CSE account.
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.
Enumerated Types
enums
s should always come after any#define
s and may be used in all files. Value names should always be in snake case with upper-case letters, andtypedef
ed.
Enumerated Types
enums
s should always come after any#define
s and may be used in all files. Value names should always be in snake case with upper-case letters, andtypedef
ed.
Enumerated types are created using enum
s and allow us to create related constant values
with a name and type safety. We give these related values a type name near the start of our
file and names for each value of the type then we
can use the values in the rest of the file. This means that if we want to change
the values at any point, we only need to change it once.
Enumerated types start with the first element having a value of zero, and each subsequent value
being incremented by one, but this can be changed by setting the first element on the enumerated
list or by setting values for each constant in the enumerated list.
Enumerated Types
enums
s should always come after any#define
s and may be used in all files. Value names should always be in snake case with upper-case letters, andtypedef
ed.
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.
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.
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.
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 as loop counters.
Declaring Variables
Variables should be declared when they are first assigned a value, as part of assigning the value. A default assigned value should only by used when a default value may be returned.
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.
Type Casting
Explicit type casting should not be used.
Type Casting
Explicit type casting should not be used.
It is possible to explicitly change the type that C considers a variable to have, using a type cast. It is very unlikely that you will need to make an explicit type cast in this course.
Assignments in Conditions
=
should only be used to assign values to a single variable, not as an operator. Assignment should not be used as an expression and should not be used as the condition in anif
statement orwhile
loop.
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.
Switch
switch
andcase
should not be used; useif
…else if
…else
instead.
Switch
switch
andcase
should not be used; useif
…else if
…else
instead.
switch
is an old compiler hack;
it and case
have subtle and confusing behaviour
and you should avoid 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, as with for
,
to construct do...while
expressions
that are confusing and poorly structured,
and in COMP1511 we prefer to avoid them.
For Loops
for
loops should not be used.
For Loops
for
loops should not be used.
Many C textbooks like for
loops.
Their terseness means you can cram
more meaningless waffle onto a page,
without actually considering
the problem you’re setting out to solve.
And, as noted above,
our style guide requires that
a single line of code should only
do one thing or have one effect;
for
loops demonstrate the exact opposite.
Worse, in reading for
loops,
the order of execution is not strictly linear:
the increment portion of the loop
always occurs after the body of the loop,
whilst the statement itself appears before it.
It’s much easier to construct
a confusing and poorly structured for
loop,
and in COMP1511 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 Names
Function names must use camel case starting with a lower-case letter.
Function Names
Function names must use camel case starting with a lower-case letter.
When writing functions for ADTs, you may want to keep you 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.
Function Return
The last statement of a function that returns a value (non-
void
) must be areturn
statement. Noreturn
statement should be used for functions that do not return a value.Aside from the final statement of a function, a
return
statement may only be used to exit a function early to handle an error such as whenmalloc
or a related function returns aNULL
value due to a memory error.
Module Functions
Functions from modules and ADTs that can be used by other .c files should have their prototype in a .h file.
Functions from modules and ADTs which are only used inside the module or ADT .c file should have their prototype at the top of the .c file and should be marked with the
static
keyword.Excluding constructors (like
createType(void)
, which returns a new instance of the abstract type), ADT functions must always take the abstract type as their first argument.
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.
Const Arguments
const
arguments and types should not be used.
Structs
Struct Order
struct
and type definitions should always follow constant definitions, come before function prototypes, and should only be found in .c files.
Struct Order
struct
and type definitions should always follow constant definitions, come before function prototypes, and 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 be wrapped in atypedef
. Thestruct
tag should be named in camel-case starting with an underscore, followd by a lower-case letter. The type name should be the same as the struct tag but without the leading underscore.
Struct naming
struct
s should always be wrapped in atypedef
. Thestruct
tag should be named in camel-case starting with an underscore, followd by a lower-case letter. The type name should be the same as the struct tag but without the leading underscore.
The names of struct
tags must be in camel case starting with an underscore,
followed by a lower-case letter.
A struct
should always be wrapped in a typedef
so that it can be
used more cleanly as a type. The type name should be the same as the struct
tag, but without the underscore at the start. This type that wraps the
struct
is called the concrete type.
Struct naming
struct
s should always be wrapped in atypedef
. Thestruct
tag should be named in camel-case starting with an underscore, followd by a lower-case letter. The type name should be the same as the struct tag but without the leading underscore.
Pointers to Structs
A
typedef
of a pointer to a struct should always appear immediately before the structtypedef
or in a separate.h
file, and should use the struct tag. The name of the type should be the same as the name of thestruct
type but starting with an uppercase character.
Pointers to Structs
A
typedef
of a pointer to a struct should always appear immediately before the structtypedef
or in a separate.h
file, and should use the struct tag. The name of the type should be the same as the name of thestruct
type but starting with an uppercase character.
The struct pointer type
should reflect the name
of the struct it refers to.
The typedef
of a pointer to to a struct
is called the abstract type.
The uppercase letter helps to distinguish between concrete and abstract types.
Pointers to Structs
A
typedef
of a pointer to a struct should always appear immediately before the structtypedef
or in a separate.h
file, and should use the struct tag. The name of the type should be the same as the name of thestruct
type but starting with an uppercase character.
Unions
union
s should not be used.
Multiple Files and ADTs
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
.
ADT File
ADT files must have the same name as the type for the .h and .c file, and the name must be in camel case starting in a upper-case letter. For example, the type
MyDataType
would be found inMyDataType.c
andMyDataType.h
.
ADT File
ADT files must have the same name as the type for the .h and .c file, and the name must be in camel case starting in a upper-case letter. For example, the type
MyDataType
would be found inMyDataType.c
andMyDataType.h
.
Later in the course, we will look at abstract data types (ADTs), which let us
create our own complex data types in a way that makes them easy to use. Each ADT
will have its own pair of files, much like the module, but the names will
start with an upper-case letter and have the same name as the type they
describe. For example, if I had files describing the ADT UsefulDataType
, then
they would be called UsefulDataType.h
and UsefulDataType.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.
Pointers
Declaring Pointers
When writing pointer types for variable or arguments, the
*
should always be attached to the variable name rather than the type.
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, []
.
Example
Here is an example of how your code should look:
Security
System Calls
System calls should not be used.
Files and I/O
File and similar I/O should not be used unless explicitly asked for.
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.