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.

// 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>

// 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)
   
Enumerated Types

enumss should always come after any #defines and may be used in all files. Value names should always be in snake case with upper-case letters, and typedefed.

Enumerated Types

enumss should always come after any #defines and may be used in all files. Value names should always be in snake case with upper-case letters, and typedefed.

Enumerated types are created using enums 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

enumss should always come after any #defines and may be used in all files. Value names should always be in snake case with upper-case letters, and typedefed.

typedef enum {
    RED,
    GREEN,
    BLUE
} color;

typedef enum {
    ACE = 1,
    TWO, THREE, FOUR,
    FIVE, SIX, SEVEN,
    EIGHT, NINE, TEN,
    JACK, QUEEN, KING
} value;

value v = NINE;
color c = RED;

   
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.

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.

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.

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.

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.

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 an if statement or while 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 and case should not be used; use ifelse ifelse instead.

Switch

switch and case should not be used; use ifelse ifelse instead.

switch is an old compiler hack; it and case have subtle and confusing behaviour and you should avoid 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, 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.

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

// 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);
   
Function Return

The last statement of a function that returns a value (non-void) must be a return statement. No return 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 when malloc or a related function returns a NULL 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 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 be wrapped in a typedef. The struct 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

structs should always be wrapped in a typedef. The struct 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

structs should always be wrapped in a typedef. The struct 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.

#define MAX_NAME_LEN 256

typedef struct _antFarmer {
  int age;
  char name[MAX_NAME_LEN];
} antFarmer;
   
Pointers to Structs

A typedef of a pointer to a struct should always appear immediately before the struct typedef 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 the struct type but starting with an uppercase character.

Pointers to Structs

A typedef of a pointer to a struct should always appear immediately before the struct typedef 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 the struct 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 struct typedef 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 the struct type but starting with an uppercase character.

#define MAX_NAME_LEN 256

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

unions 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, 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.

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 in MyDataType.c and MyDataType.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 in MyDataType.c and MyDataType.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 #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

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:

// 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 enum {
    ONE = 1,
    TWO, THREE, FOUR,
    FIVE, SIX, SEVEN
} value;

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 = min(argA, 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

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.

#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);
}