Variables, Types, Printing to the Console
You can access the slides 🖼️ for this lecture. All the code samples given here can be found online, alongside instructions on how to bring up the proper environment to build and execute them here. You can download the entire set of slides and lecture notes in PDF from the home page.
In this lecture we talk about variables, types, and printing to the console in C.
Variables
Here is an example of C code with a main function using a local variable named a
:
#include <stdio.h>
int main() {
int a; // declare a of type int (signed integer)
a = 12; // set the value of a to 12
printf("a's value: %d\n", a); // print the value of a
return 0;
}
The variable is set to 12 and then printed to the standard output.
Variables have a name, here it's a
.
Variables also have a type, here it's int
corresponding to a signed integer.
Finally, variables have a value, which can generally evolve as the program executes.
Before being able to use a variable, it must first be declared.
A variable declaration is a statement defining the name and type of the variable.
Here we declare a
in the first line of code of the main
function.
In C a variable name should start with a letter or underscore and contain a combination of letters, underscores, numbers.
Try to be descriptive, for example here sum
or final_balance2
are much more meaningful than just a
.
Using Variables
Here are a few examples of variables usage:
int a; int b; int c;
int d = 12; // declare and set
int x, y = 10, z = 11;
a = 12; // set a to 12
b = 20; // set b to 20
c = 10 + 10; // set c to 20
a = b; // a = 20
d++; // d = d + 1
y *= 2 // y = y * 2;
We first declare 3 integers a
, b
and c
.
A variable can be declared and set in one statement, this is what happens to d
.
On the third line we declare x
, y
and z
and we also set the value of y
to be 10 and the value of z
to be 11.
On the code on the right we have an example of arithmetic operation, setting the value of c
to be 10+10
.
We also set the value of a
to be the value of b
.
And we also have some nice shortcuts for commonly-used operations.
For example by using d++
we effectively increment d
by 1.
Also, y *=2
corresponds to multiplying y
by 2 and storing the result in y
.
There is a similar construct for addition, subtraction and division.
Types
Each variable must have a type. Types are used by the compiler to do two things. First, the compiler performs some checks on the operations realised on the variable. For example in some cases it is able to warn about overflows when one tries to store a number larger than the size of the target variable type. Second, allocate the memory space for the variable. Each type defines a given size in bytes for this.
Types Define Data Storage Size in Memory
At runtime one can see the program's memory as a gigantic array of bytes.
As an example, the int
type, on the x86-64 architecture that most of our laptop/desktop computers are using, is 4 bytes long.
So the compiler reserves 4 bytes in memory when declaring e.g. int a
.
That memory will be used to store a
's value at runtime:
Primitive Types
Beyond int
, other basic types (named primitive types) include characters (char
) and floating point numbers (float
for single- and double
for double-precision).
We can describe a constant character in the code, here x
, with single quotes.
When performing an arithmetic operation on an integer and a float
, the compiler will promote the result to a float
(same for double
).
For example here float_res
will include a decimal part:
int int1 = 2, int2 = 4;
float float1 = 2.8;
// when mixed with floats in arithmetics, integers are promoted to floats:
float float_res = int1 + float1;
float float_res2 = int1 * (float1 + int2);
// when stored in an integer variable, floats are _truncated_:
int int_res = int1 * (float1 + int2);
Note the use of the parentheses to set the order of evaluation in the second example setting float_res2
.
Even if the right-hand side is promoted to a float in the assignment of int_res
, because of its type (int
), the result will be truncated.
More Types, Qualifiers
When declaring a variable, we use a combination of a type and optional qualifiers, that will define what the variable can hold and how much space is reserved. It is very important to be aware of the storage size of variables, as things like overflows can have very nasty consequences. Here are a few examples with the information that the C standard^[https://en.wikipedia.org/wiki/C_data_types] gives about the corresponding storage sizes:
short int a; // signed, at least 16 bits: [-32,767, +32,767]
int b; // signed, at least 16 bits: [-32,767, +32,767]
unsigned int c; // unsigned: [0, +65,535]
long int d; // at least 32 bits: [-2,147,483,647, +2,147,483,647]
unsigned long int e; // unsigned: [0, +4,294,967,295]
long long int f; // at least 64 bits: [-9x10^18, +9x10^18]
long long unsigned int g; // unsigned: [0, +18x10^18]
float h; // storage size unspecified, generally 32 bits
double i; // storage size unspecified, generally 64 bits
For example int
is signed (i.e. it can hold positive as well as negative integers) and should be at least 2 bytes, so it can store numbers from -32,767 to 32,767.
unsigned int
has the same size and is unsigned, so it can store more, but only positive, numbers.
Note that the storage size information coming from the standard are rather imprecise.
The actual sizes depend on the architecture of the CPU the program is compiled to execute upon.
sizeof
To get the exact storage size we can use the sizeof
function, that takes a type as parameter and returns its storage size in bytes:
int so_short = sizeof(short int);
int so_int = sizeof(int);
int so_uint = sizeof(unsigned int);
int so_long = sizeof(long int);
int so_longlong = sizeof(long long int);
int so_float = sizeof(float);
int so_double = sizeof(double);
printf("size of short: %d bytes\n", so_short);
printf("size of int: %d bytes\n", so_int);
printf("size of unsigned int: %d bytes\n", so_uint);
printf("size of long int: %d bytes\n", so_long);
printf("size of long long int: %d bytes\n", so_longlong);
printf("size of float: %d bytes\n", so_float);
printf("size of double: %d bytes\n", so_double);
On the Intel x86-64 architecture:
- The size of
short
is 2 bytes. - The size of
int
,unsigned int
as well asfloat
are 4 bytes. - The size of
long
,long long int
, anddouble
are 8 bytes.
In memory things look like that:
Printing to the Terminal
We have been using printf
to print a lot of things to the console, let's see how it works more in details.
It takes one or more arguments:
printf(<format string>, <variable1>, <variable2>, etc.);
The first argument is the format string containing the text to print as well as optional markers that will be replaced with variables' values The next arguments are optional. It is the list of variables which value needs to be printed, 1 variable per argument. The format string marker to use depends on the corresponding variable type. A few examples:
int int_var = -1;
unsigned int uint_var = 12;
long int lint_var = 10;
float float_var = 2.5;
double double_var = 2.5;
char char_var = 'a';
char string_var[] = "hello";
printf("Integer: %d\n", int_var);
printf("Unsigned integer: %u\n", uint_var);
printf("Long integer: %ld\n", lint_var);
printf("Float: %f\n", float_var);
printf("Double: %lf\n", double_var);
printf("Characters: %c\n", char_var);
printf("String: %s\n", string_var);
printf("Several variables: %d, %lf, %s\n", int_var, double_var, string_var);
Various markers are illustrated in that example:
%d
is used for signed integers,%u
for unsigned ones.%l
is used to indicate thelong
qualifiers, for example we have%ld
for asigned long int
.%f
is used for floats and%lf
for doubles.- Finally, notice the declaration of a string with the brackets for the variable name and the double quotes for the string itself.
We can print it with
%s
.