What are some common C style guides?

Python’s formatting is still not as opinionated as it could be! Golang has very strict formatting rules. C is relatively free for all, allowing you to define your own style. There are lots of different style choices and style guides.

Is the opening code block for a C program mandatory?

    #include <stdio.h>
    int main(int argc, char *argv[])

#include <stdio.h> is used to include another file, similar to imports in python, which defines values, functions and constants. In this case, the header file stdio.h contains declarations for the printf function, scanf function and other related functions for input and output. The declaration for the main function is made because that is the function that is called when the program is executed. The declaration states it returns a value that is an integer (int main(...)), and also takes two arguments, argc, which is an integer, and argv, which is an array of strings. These values are used when the program is executed from the command line - e.g. ./program argument1 argument2 argc is passed into the program when it is executed as the number of arguments provided on the command line + 1, because the program will always take its name as an argument (effectively always having at least 1 argument) argv is passed as the value of the arguments, which is an array of char arrays, which are like strings. In the previous example, this array would be passed in with the values “program”, “argument1” and “argument2”. The integer value main returns is an exit code, which can be used to indicate if your program ran correctly. The exit code for successful execution is 0, so C programs contain a return 0; line to mark successful execution.

When you use #define, do have to specify the type the constant would be?

When the compiler compiles the code, instead of using the defined values as a constant, it instead essentially pastes whatever value is defined there.

In the code shown below, rather than the compiler seeing two integers (‘1’ and CONSTANT) and adding them together, it instead replaces the world CONSTANT with the value associated with it, so the code is essentially changed to int value = 1 + 5

#define CONSTANT 5
int value = 1 + CONSTANT;

This means you can even do things like referencing other variables through a constant if you use the equation often (say in a piece of complex math). The code below shows an example of this. If you’re still a little unsure, try replacing the word AREA in the main function with the value defined and this should help you visualise what is going on:

#include <stdio.h>

#define PI 3.14159265
#define AREA PI * radius * radius
#define CIRCUMFERENCE PI * radius * 2

int main() {
    double radius;
    scanf("%lf", &radius);
    printf("Area: %f\n",AREA);
    printf("Circumference: %f\n",CIRCUMFERENCE);
    return 0;
}

While creating functions that return area and circumference given one parameter (radius) would likely be a better solution, something like this truly shows the versatility of what you can do with the C language

Why don’t we just use string.h instead of writing our own functions?

Sure, you can use them, but you’ll learn far more by writing the functions you use from scratch. On the other hand, it’s good to know what library functions exist and how to use them. For assessments we will specify which library functions you can/can’t use.

How can I continue code onto the next line?

The backslash character \ can be used to continue a line of code onto the next line.

I fixed my code by adding a semicolon, but the compiler still gives me an error. What’s wrong?

Did you remember to save your file?

How do I choose the name of the output file?

You can use the -o flag to specify the name of the output file.

So on the whole try gcc test.c -o test.exe to produce test.exe

Where do I write code?

In lectures the c files are written using a text editor (VSCode recommended) and then saved in a folder somewhere. If you want to do this on the command line only, you can use a command line editor such as vim or nano (changing to the folder you want to create the files in and running vim cfile.c or nano cfile.c to create and edit the file). In the terminal you need to navigate to this folder to compile it. Assuming you’re using a shell like bash, useful commands are:

  • cd <location> - change the current directory to be location. For example, if your c file is in \path\to\file\cfile.c, you would run cd \path\to\file\
  • pwd - print working directory, outputting the current location the shell is in.
  • ls - list all files in the current working directory.
  • find location -name "filename.c" - will search for a file in location with the name filename.c, and output the locations of any matches.

What is the difference between const and #define and which one is preferred?

As mentioned in the student answer, a #define is a constant expression: it is not a variable but a string that will be copied into your code, at compile time, at any location where its identifier occurs.

A const, on the other hand, is a variable: it can be used in the same way as other variables with the exception that once it is initialised, its value cannot be modified.

What is abstraction and why is it important?

Abstraction is a tricky concept to explain because, well, it’s abstract!

The idea is that by changing the way you think about something, you can hide details that are not necessary to know at that point.

Let’s say I want to write a program determine what law will be pass in Australia. I could try and calculate the physics inside the brains of all the people in the country—but that’s super difficult and doesn’t solve my problem at the level I care about it.

Instead I use abstractions:

  • I abstract away the details of the humans by using psychology, which gives us a very low description of how people act
  • I abstract away the details of psychology and the complexity of all the different interactions people have by thinking about political groups (using perhaps political science)—this means I don’t have to think about the individual behaviors of all the people
  • I abstract away the political groups by thinking about political parties—this can explain the groups without having to think about all the different subdivisions
  • I abstract away the parties by thinking about the members of parliament, and I figure out what rules they follow

For example our model of parliamentarians might look as follows:

  1. A member of parliament (MP) might always vote on an law the way a party platform tells them to
  2. If there is no rule in the platform, they might vote based on how people in their district want them to
  3. A law will pass if a majority of MPs vote yes on a law

I could then write up a program or do some data analysis that has abstracted away all these small details to provide a fairly fast and effective way at guessing what laws will pass.

For each of these levels, we could theoretically write up a set of rules, but one of the key ideas in computer science is to find the level that most effectively solves your problem and focus on the rules that actually make the biggest difference.

We could also combine two of the levels—lets say we build our model of an MP, but they’re in a case where rule 2 applies. How do we know what rule 2 tells them to do? We can use our model of political groups (which covers the district).

Representing this as a program we might have a few functions:

get_party_platform(issue x, members_party p);
will_law_pass(issue x, members m[]);
get_district_view(which_district d, member m[]);
has_majority_vote(issue x, members m[]);

Abstraction is the technique we use to turn our problem into this set of functions, some of which call each other and rely themselves on other abstractions.

I’m running ./program.c and it says “command not found”?

You can’t run a .c file, you first need to compile the program. You can do this by running gcc program.c -o program and then running ./program.

How do I check how long a program takes to run and how much CPU and memory it uses?

You can use the time command to check how long a program takes to run. For example, if you want to check how long the program program takes to run, you can run time ./program.

The `top’ command on Unix/Linux/Mac can show you memory usage at any given point in time.

If you want to know in general how long/how much memory a program will take, you could try running simulations, keeping track over a long period of time, or doing mathematical analysis.

You can also use software called a profiler to track a program over a span of time–but this is a bit more advanced.

Can you explain EXIT_SUCCESS and EXIT_Failure?

EXIT_SUCCESS and EXIT_FAILURE are constants defined in stdlib.h. They are used to indicate whether a program has run successfully or not. EXIT_SUCCESS is defined as 0 and EXIT_FAILURE is defined as 1. If a program runs successfully, it should return EXIT_SUCCESS. If a program fails, it should return EXIT_FAILURE.

To use them in your program, you need to include stdlib.h at the top of your program. Then, you can use them in your main function. For example:

#include <stdlib.h>
int main(int argc, char *argv[]) {
    // do stuff
    return EXIT_SUCCESS;
}

What is the difference between an algorithm and a function?

You’ve stumbled upon some of the really deep links between mathematics and computer science.

Both mathematics and programs are types of ‘formalisms’ or ‘formal systems’—that identify a set of rules for transforming abstract objects.

But further than that, there are perfect equivalences between programs and a variety of mathematical formalisms. For example, the Curry-Howard correspondence proves that all programs correspond to a mathematical proof (using particular objects).

When you reference functions of functions, this is a really nice pointer to category theory that concerns itself with such higher-types of objects, and can be shown to be another equivalent formalism for programs—proven under the Curry-Howard-Lambek correspondence.

When we talk about algorithms, we’re referring generally to classes of programs that are designed to abstract away some of the details that might be specific to a given situation, but capture a broader ‘problem’.

This is all a wonderful intro to the theory of computation, some of which is covered in the 3rd year subject “Models of Computation”.

Given you’re already on this track, I highly recommend Godel Escher Bach by Douglas Hofstader, which is enjoyable light reading (Pulitzer winner) that helps your brain ‘Grok’ some of these isomorphisms.

Does the compiler typically guarantee the order in which variables are stored in memory?

The compiler makes no such guarantees. We use variable names as an abstraction for manipulating memory when coding in C, so there’s no need for us to use their underlying memory addresses (with some caveats).

For this reason, the compiler is free to make optimisations, arranging memory however it pleases.

However, arrays are stored in memory in the order they are declared. For example, if we have an array of 10 integers, the first integer will be stored at the lowest memory address, and the last integer will be stored at the highest memory address.

For more on this see https://stackoverflow.com/a/58154809

“I understand that “.” is shorthand for the current directory, but what does “/” stand for? Why does “./” together just make the compiled program run? Additionally, I am confused about the variable “PATH”. Is it a pre-defined location where the computer automatically looks for things?

”./” means under the current directory. ‘/’ in particular means “under”. Without it, ‘.’ can be used as part of a file name, and the system won’t be able to tell that this is now referring to “the current directory”.

When we type a string in the terminal and hit “Enter”, the string is interpreted as a command, which essentially is a program lying somewhere in the system, to be executed. By default, the system starts looking under the directory specified by the $PATH variable, which is a system variable that we can set, just like we can set which desktop image to be used by the system.

Adding the prefix “./” tells the system to start looking under the current directory. If there is a hit, run that program.

Can arrays be variable length in C?

Modern C does have variable-length arrays but they are generally considered unwise to use, and we don’t cover them in the course. For more reading see https://en.wikipedia.org/wiki/Variable-length_array

Is there any way to reset getchar() to the beginning of the input/buffer/text from input user?

Once you’ve read a character in, it’s gone!

You should read in the input, in such a way, so as to allow you to access all the data you need without rereading it.

(Technical note: there is actually a way to put characters back in the input buffer, but it is bad practice and so I won’t describe it here)

Why aren’t we allowed to use global variables in assessments?

We use the restriction on global variables as a teaching tool. They are useful in some situations, but they can also be a source of bugs, and restricting their use encourages you to think about how to best break up your program.

What does flushing a file mean?

Due to the slow speed of writing data into files on hard disks (as it involves physically turning the disk writing head etc.), fprintf() and fwrite() do not write contents directly into files every time they are called. Instead, the contents are written into a piece of memory call an output buffer. Only when the buffer is full or when fclose() is called (or fflush() is called) the data gets pushed (“flushed”) onto the hard disk.

I am running my code in two different places (eg; my laptop and Grok/Ed) and I am getting different results. Why is this?

This is typically due to a bug in the way your code handles memory (like variables you forgot to initialize, or writing outside the bounds of an array).

Different compilers and systems produce slightly different programs from the same code, so even if a bug is present, your computer might accidentally get lucky and not be impacted by it sometimes.

Doesn’t mean the bug isn’t there!

I get the following compiler error: “linker command failed with exit code 1 (use -v to see invocation)” What does this mean?

Note: This contains un-assessable material and is here to help you understand why you might get this error and what it means.

clang and gcc do a number of things:

  • preprocess the code (replacing things like #defines)
  • combine code from as many files as you tell it to
  • convert the code you wrote (in a multistage process) into a binary representation of assembly language that the CPU can process
  • run a program called a linker to connect your program to any static and dynamic libraries that you used in your code. This command is typically ld.

What are libraries? These are just like libraries in python that you import, and would reference in your code via #include statements. Not that the include statement references some library code, but if you’re using a non-default library you have to tell the compiler which library you are using, and where to find it. The compiler will then pass this information on when it runs the linker.

Now, there are two main ways of including functions from a library:

  1. Have the compiler copy the entire machine code of the library into your own program, which requires the library to be written in a way that makes it easy to include: static linking with static libraries.
  2. Have the compiler mark where the library should be used, and add some data into the program that tells the operating system to load the library (and where to find it) when you run your program: dynamic linking with dynamic libraries. When the program is run, the operating system will see that the program uses dynamic libraries, and it will run an extra program called a dynamic linker to load the library into the computer’s RAM and replace all references to the library in your program with the address of where it put the dynamic library

Why do (2)? It lets you keep only a single copy of the library around, and means you can have smaller programs

Why do (1) (what I think we should be doing now)? Less fussing around with lots of nonsense, and memory is super cheap now, and avoids a bunch of security problems So why am I getting an error “linker command failed”?

This is happening after the first few steps of compilation, at the stage where your compiler is trying to run the linker. Unfortunately this could be any number of reasons! If you manually run the linker, you could see the specific error message that the linker is throwing.

The most common the linker is looking for libraries in the wrong places (misconfigured system)