COMP1911 23T2

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.

// A description about the program
//
// By  ... (z0000000)
// and ... (z0000000)
//
// Written on YYYY-MM-DD
//

#include <stdio.h>
#include <stdlib.h>

// Add your own #includes here

#define FALSE 0
#define TRUE  (!FALSE)

// Add your own #defines here

// Add your enums here

// Add your typedefs and structs here

// Add your function prototypes here

int main(int argc, char *argv[]) {
}

// Add your function implementations here
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.

// A description about the program
//
// By  ... (z0000000)
// and ... (z0000000)
//
// Written on YYYY-MM-DD
//
// Tutor's Name (dayHH-lab)
   
Includes

#includes should be directly after the header comment.

Includes

#includes should be directly after the header comment.

#includes are how we use code from other files in our .c files so that we can share functions between files. #includes 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.

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include "Ant.h"
#include "AntFarm.h"
    
Constants

#defines should always come after any #includes and may be used in all files. Constant names should always be in snake case with upper-case letters.

Constants

#defines should always come after any #includes 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 floats, chars, and strings.

Constants

#defines should always come after any #includes and may be used in all files. Constant names should always be in snake case with upper-case letters.

#define FARM_WIDTH  120
#define FARM_DEPTH  40
#define FARM_HEIGHT 45
#define FARM_VOLUME (FARM_WIDTH * FARM_DEPTH * FARM_HEIGHT)
   
Main Function

The main function should have the signature int 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 or EXIT_SUCCESS on a successful exit. The main 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 signature int 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 or EXIT_SUCCESS on a successful exit. The main 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 signature int main(int argc, char *argv[]) {} and should return EXIT_SUCCESS on a successful exit. The main function should be the first function in a file where it is used and must follow all function prototypes.

int main(int argc, char *argv[]) {
    return EXIT_SUCCESS;
}
   

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.

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++;
    }
}
   
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 as char *name[MAX_LENGTH]. Arrays should not be declared with a size using a variable, such as int 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.

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

char c = getchar();
while (c != EOF) {
    // do something
    c = getchar();
}

Node curr = list;
while (curr != NULL) {
    // do something
    curr = curr->next;
}
   
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 or continue should not be used in loops.

Break and Continue

break or continue 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; use while (condition) {} instead.

Do While

do {} while (condition); should not be used; use while (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.

// Counts the number of ants in the ant farm
int countAnts(AntFarm farm);
   
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);
   
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 structs, 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

structs should always have an associated typedef. The struct 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

structs should always have an associated typedef. The struct 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

structs should always have an associated typedef. The struct 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.

#define MAX_NAME_LEN 256

typedef struct antFarmer AntFarmer; 
struct antFarmer{
  int age;
  char name[MAX_NAME_LEN];
};
   
Unions

unions 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, like myProgram.c.

Program File

File names with a main function must be named in camel case starting with a lower-case letter, like myProgram.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 and myModule.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 and myModule.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 #defined. 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.

// An ant farm simulator
// Julian Saknussemm (z3141592)
// Tutor: Jennifer Brown (sat23-moog)
// Created on 2017-07-25

#ifndef ANT_FARM_H
#define ANT_FARM_H

// Define some constants

// Define some abstract types

// Define some interface functions

#endif //ANT_FARM_H

Example

Here is an example of how your code should look:

// A description about the program
//
// By  ... (z0000000)
// and ... (z0000000)
//
// Written on YYYY-MM-DD
//
// Tutor's Name (dayHH-lab)

#include <stdio.h>
#include <stdlib.h>
#include "SomeType.h"

#define FALSE 0
#define TRUE  (!FALSE)

#define NUM_MULTIPLES 12;
#define NAME_LENGTH 256


typedef struct _anotherType *AnotherType;
typedef struct _anotherType {
    int member;
    char name[NAME_LENGTH];
} anotherType;

int functionName(int argA, int argB);
int getMultiplier(int argA, int argB);

int main(int argc, char *argv[]) {

    // some other code here...

    return EXIT_SUCCESS;
}

int functionName(int argA, int argB) {
    int multiplier = multiplier(argA, argB);

    int value =  argB;
    int multiples[NUM_MULTIPLES];
    int i = 0;
    while (i < NUM_MULTIPLES) {
        value *= multiplier;
        multiples[i] = value;
        i++;
    }

    return average(multiples, NUM_MULTIPLES);
}

int getMultiplier(int argA, int argB) {
    int multiplier;
    if (argA > argB) {
        multiplier = argA - argB; 
    } else if (argA == arg B) {
        multiplier = 1;
    } else {
        multiplier = max(argA, argB);
    }

    return multiplier;
}

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.

#include <stdio.h>

#define BUFFER_LENGTH 512

int main(int argc, char *argv[]) {
    // Read some text
    char someText[BUFFER_LENGTH];

    // This is UNSAFE! A long enough word could overflow the buffer.
    scanf("%s", someText);

    // This is UNSAFE!
    gets(someText);

    // This is safe
    scanf("%511s", someText);

    // This is safe
    fgets(someText, BUFFER_LENGTH, stdin);
}
   
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.

#include <stdio.h>

#define BUFFER_LENGTH 512

int main(int argc, char *argv[]) {
    // Read some text
    char someText[BUFFER_LENGTH];
    fgets(someText, BUFFER_LENGTH, stdin);

    // This is _very_ bad
    printf(someText);

    // Use this instead
    printf("%s", someText);
    
    // This could also be used
    fputs(someText, stdout);
}