This page lists the code-quality guidelines used when reviewing Exercise Volumes.
The guidelines apply to code you write or change.
Important
This page does not define if your code for a given exercise is ‘correct’. Each exercise specifies its own required behaviour, inputs, outputs, and other correctness
requirements. This page only defines general requirements for your programs.
Any guideline on this page (and others that we may add) may be enforced during code review. TAs may comment on any guideline. If a TA identifies a violation, you must fix it
before resubmitting, or discuss with the TA if you believe the guideline
does not apply.
When the guidelines apply: If a guideline mentions concepts that we haven’t covered, you can ignore it
You don't have to be perfect - we'll teach you this over time!
This might seem overwhelming at first - don’t worry! Throughout your labs, your TAs will work with you and give you comments and ideas on how to express clear code intent in your programs. This takes time!
Format all submitted C source files and pass the exercise formatter checks.
G0: Compile and terminate without warnings
Your program must always compile successfully. Overall, CS1010 compiles C code using the C23 standard with -std=c23. We may add “flags” into your Makefile or ask you to add them yourself for specific exercises: we will grade your code based on these flags!
Even if your code compiles, the compiler should not emit any warnings. Similarly, we may change the flags we define for your exercise.
Your program must also always terminate (e.g., no infinite loops) when running on valid inputs as specified by the exercise.
G1: Avoid Undefined Behaviour
Submitted code should not (under reasonable circumstances) execute behaviour that the C23 standard calls “undefined behaviour”. This includes, for example, dividing by zero, reading uninitialized storage, using an object after it has been freed, and freeing an object twice.
Simple example
int x = 1 / 0; // Undefined behaviour: dividing by 0
Why: Once a program has undefined behaviour, the C standard places no
requirements on its result. The program may appear to work, fail unpredictably,
or be transformed unexpectedly by the compiler.
When sanitizers are enabled, the program must produce no sanitizer reports.
This requirement applies even when the program prints the expected output.
For your convenience, this is all the UB discussed so far
Up to and including Lecture 5: Pointers and Strings.
This is a recap of what we have covered, not a complete list of UB in C.
Arithmetic and Functions
Integer division or remainder by zero: evaluating x / 0 or x % 0
Signed integer overflow: computing a result outside the range of its signed type, for example int x = INT_MAX; x = x + 1;. Unsigned arithmetic wraps around and is not itself UB, but using the wrapped value as an array index can lead to UB
Using a missing return value: reaching the end of a non-void function and then using its return value. Make sure every relevant path returns a value. Reaching the end of main is a special case: it returns 0
Passing the wrong argument type to printf: match each argument to its format specifier. For example, %d expects an int, not a double; to print an object’s address with %p, use a suitable pointer conversion, such as printf("%p", (void *)&x)
Unsequenced changes to the same variable: for example, a = a++ modifies a twice without the required ordering. Keep the increment and assignment in separate statements
Arrays and Initialization
Reading an uninitialized value: for example, int x; printf("%d", x);, or reading an element of an uninitialized local array before assigning to it
Accessing outside an array’s bounds: for int a[5], only a[0] through a[4] are elements. Reading or writing a[-1] or a[5] is UB, even if nearby memory happens to be accessible
Pointers and Strings
Dereferencing a null or uninitialized pointer: for example, int *p = nullptr; *p = 5; or int *p; *p = 5;. A pointer must refer to a valid, live object before you read or write through it
Pointer arithmetic outside the permitted range: the result must stay within the same array or one past its end. You may form a one-past pointer, but must not dereference it. Subtract pointers only within the same array (including its one-past position)
Accessing an object after its lifetime ends: for example, using a pointer to a local variable or array after its function returns, or after the block declaring it ends
Modifying a string literal:char *p = "hi"; p[0] = 'H'; is UB. Use char p[] = "hi"; if you need a modifiable array
Reading or writing past a character array when working with strings: functions such as strlen and printf("%s", s) need a terminating '\0' within accessible storage. A destination for strcpy needs room for all the characters and the terminator
Remember: getting the expected output does not make any of these operations safe.
G2: Make code intent clear
This is potentially the most extensive (and maybe subjective) rule - but it’s extremely important!
Make the intent of your code clear through comments, structure, and the way the
code is written. Use comments mainly for for important reasoning and decisions. Name your variables appropriately. Divide program flow into manageable, logical pieces, and keep
function interfaces focused and appropriately encapsulated.
Why: Clear comments, focused structure, and precise interfaces make the
program’s purpose and assumptions visible. They help readers understand the
code without reconstructing unnecessary indirection or unrelated implementation
details.
G3: Use appropriate numeric types
For CS1010’s numeric types (by default):
Use char by default for character types
Use int by default for integral types
Use unsigned int if you know the value is always non-negative
Use double if your values might not only be whole numbers
G4: Use const when a value or pointee should not be modified
Use the const modifier when a value or pointee should not be modified. This
makes your intent clear and allows the compiler to catch accidental changes.
G5: Use booleans instead of integers where applicable
C23 provides bool, true, and false directly.
Examples
Bad:
long found = 0;found = 1;if (found != 0) { print_result();}
Good:
bool found = false;found = true;if (found) { print_result();}
Why:bool, true, and false show that a value represents a condition,
so the code is easier to read and less likely to treat arbitrary numbers as
flags.
G6: Avoid global variables where possible
When there is no good reason for state to exist outside a function, keep it
local and pass it through parameters and return values. Avoid global variables
where possible. State that must be shared by a whole
module may have a good reason to exist outside a function.
Examples
Bad:
long total;void add_to_total(long value) { total += value;}
Good:
long add_to_total(long total, long value) { return total + value;}
Why: Parameters and return values make a function’s dependencies visible,
which makes the function easier to understand, test, and reuse. Mutable global
state hides dependencies and makes functions harder to test and reason about.
if (value < 0) { goto invalid;}process(value);invalid:print_error();
Good:
if (value < 0) { print_error();} else { process(value);}
Why: Structured control flow keeps the paths through a function visible
and makes it easier to reason about initialization, cleanup, and error cases.
G8: Don’t leak memory
Do not leak memory (e.g., it must be freed by the end of the program). We will specify in your labs / volumes whether your code is responsible for freeing certain memory, or whether our code will do so instead,
Why: Releasing owned memory when it is no longer needed prevents the
program from wasting memory during long-running work or repeated operations.
G9: Read input safely
Do not use scanf anywhere in code you write or change. This is a complete
prohibition, including when the input appears simple or bounded. When an
exercise requires input reading, use getline or fgets to read a line, then
use sscanf where appropriate to parse it.
Why: Reading a bounded line first separates input from conversion, limits
how much data is read, and makes it possible to check whether parsing
succeeded.
fgets bounds the input read, and sscanf parses the line that was read. Use
getline when the exercise requires input lines of unbounded length.
G10: Format source consistently
Format all submitted C source files. WebTop formats C files on save in Vim,
Neovim, and the supported nano save flows. You can also run make format.
Submission checks reject code that would change when formatted.
Examples
Before formatting:
if (count>0){print_count(count);}
After formatting:
if (count > 0) { print_count(count);}
Why: One formatter gives everyone the same layout, keeps diffs focused on
meaningful changes, and lets automated checks enforce the standard reliably.
The formatter uses the .clang-format file supplied with the exercise. Run
make format after the final edit and review the result.