You are on page 1of 104

The fundamentals of C

The fundamentals of C

Paul Alexander Bilokon, PhD

Thalesians Ltd
Level39, One Canada Square, Canary Wharf, London E14 5AB

2023.01.18
The fundamentals of C
Preliminaries

Acknowledgement

I These notes are based in part on a short course taught by my tutor, the late
Dr Gabrielle Sinnadurai.
The fundamentals of C
Preliminaries

C versus C++
I C is a classic low-level procedural programming language.
I C++ is a superset of C that is procedural, object-oriented, and more.
I Many beginning programmers will learn C before C++ due to the straightforwardness
of C.
I As a procedural language, C is often easier for beginners to grasp than C++.
I C is a simpler language with fewer options; it serves as a solid introduction to
programming.
I C is not deprecated within the industry; it is still considered an extremely universal,
portable, and efficient language.
I In general, C is used for systems-level programming, while C++ is used for higher-level
development (although, in comparison with, for example, Python, C++ is a low-level
programming language).
I A driver developer would be more likely to prefer C over C++, while a video game
developer would be almost certain to use C++ over C.
I C is somewhat closer to the assembly language (and machine code) than C++.
Therefore knowledge of C gives rise to a better appreciation of low-level programming
and the ability to write more efficient code.
I The rising popularity of low-level computing devices such as FPGAs, GPUs, TPUs,
and IPUs in finance is yet another reason to start with C rather than C++.
The fundamentals of C
Preliminaries

GNU Compiler Collection


I The GNU Compiler Collection (which was once upon a time just the GNU C
Compiler) can be used to compile both C and C++.
I gcc and g++ are compiler-drivers1 of the GNU Compiler Collection.
I Even though they automatically determine which backends (cc1, cc1plus, etc.) to call
depending on the file type, unless overridden with -x language, they have some
differences.
I g++ is used to compile C++ programs; whereas gcc is used to compile C programs
and C++ programs.
I g++ can compile .c and .cpp files but they will be treated as C++ files only; whereas
gcc can compile .c and .cpp files and they will be treated as C and C++ respectively.
I When using g++ to link the object files, g++ automatically links in the standard C++
libraries; gcc does not do this.
I g++ and gcc compile with different predefined macros.
I g++ can be thought of as

gcc -xc++ -lstdc ++ -shared - libgcc

1 The driver controls the overall execution of other tools such as the compiler, assembler, and linker. Typically you do
not need to interact with the driver, but you transparently use it to run the other tools.
The fundamentals of C
Preliminaries

The C dialect

I -std= determines the language standard:


I c89, c90, iso9899:1990—ISO C 1990;
I iso9899:199409—ISO C 1990 with amendment 1;
I gnu89, gnu90—ISO C 1990 with GNU extensions;
I c99, c9x, iso9899:1999, iso9899:199x—ISO C 1999;
I gnu99, gnu9x—ISO C with GNU extensions;
I c11, c1x, iso9899:2011—ISO C 2011;
I gnu11, gnu1x—ISO C 2011 with GNU extensions;
I c17, c18, iso9899:2017, iso9899:2018—ISO C 2017;
I gnu17, gnu18—ISO C 2017 with GNU extensions;
I c2x—the next version of the ISO C standard, still under development; the support for this
version is experimental and incomplete;
I gnu2x—the next version of the ISO C standard, still under development, plus GNU
extensions; the support for this version is experimental and incomplete.
I In C mode, the command line option -ansi is equivalent to -std=c90.
I The default C language standard is gnu18.
The fundamentals of C
Preliminaries

Clang

I Clang is a C, C++, and Objective-C compiler, which encompasses preprocessing,


parsing, optimization, code generation, assembly, and linking.
I The -x <language> command line option tells Clang to treat subsequent input files as
having the given language.
I The -std=<standard> command line option specifies the language standard to
compile for. Supported values for the C language are:
I c89, c90, iso9899:1990—ISO C 1990;
I iso9899:199409—ISO C 1990 with amendment 1;
I gnu89, gnu90—ISO C 1990 with GNU extensions;
I c99, iso9899:1999—ISO C 1999;
I gnu99—ISO C with GNU extensions;
I c11, iso9899:2011—ISO C 2011;
I gnu11—ISO C 2011 with GNU extensions;
I c17, iso9899:2017—ISO C 2017;
I gnu17—ISO C 2017 with GNU extensions.
I The default C language standard is gnu17, except on PS4, where it is gnu99.
The fundamentals of C
Preliminaries

Visual Studio 2019


I By default, CL assumes that files with the .c extension are C source files and files with
the .cpp or the .cxx extension are C++ source files.
I The command line options /Tc, /Tp, /TC, /TP specify the source file type.
I The syntax is

/Tc filename
/Tp filename
/TC
/TP

I The /Tc option specifies that its filename argument is a C source file, even if it does
not have a .c extension.
I The /Tp option specifies that its filename argument is a C++ source file, even if it does
not have a .cpp or .cxx extension.
I A space between the option and the filename is optional. Each option specifies one
file; to specify additional files, repeat the option.
I /TC and /TP are global variants of /Tc and /Tp. They specify to the compiler to treat
all files named on the command line as C source files (/TC) or C++ source files (/TP),
without regard to location on the command line in relation to the option.
I These global options can be overridden on a single file by means of /Tc or /Tp.
The fundamentals of C
Preliminaries

Compile as C code
The fundamentals of C
Preliminaries

The language dialect in MSVC

I The /std option is available in Visual Studio 2017 and later.


I It is used to control the version-specific ISO C or C++ programming language standard
features enabled during compilation of your code.
I This option allows you to disable support for certain new language and library features:
ones that may break your existing code that conforms to a particular version of the
language standard.
I By default, when code is compiled as C, the MSVC compiler doesn’t conform to a
particular C standard.
I It implements ANSI C89 with several Microsoft extensions, some of which are part of
ISO C99.
I Some Microsoft extensions can be disabled by using the /Za compiler option, but
others remain in effect. It isn’t possible to specify strict C89 conformance.
I Starting in Visual Studio 2019 version 16.8, you may specify /std:c11 or /std:c17
for code compiled as C.
I These options specify conformance modes that correspond with ISO C11 and ISO
C17.
The fundamentals of C
Getting started

hello world.c

Let us consider a simple C program.

# include <stdio.h>

/*
The main function :
the designated entry point to the program .
*/
int main(void)
{
printf ("Hello World !\n");
return 0;
}
The fundamentals of C
Getting started

The first “hello, world” program

According to Chuck Herbert, Stack Overflow, 2018.07.032 ,


Brian Kernighan actually wrote the first “hello, world” program as part of the documentation for
the BCPL programming language developed by Martin Richards. BCPL was used while C was
being developed at Bell Labs a few years before the publication of Kernighan and Ritchie’s C
book in 1972.
As part of the research for a book I was writing about the Alice programming environment, I
corresponded with both Prof. Kernighan at Princeton and Martin Richards at Cambridge (when
I was teaching a seminar there in the 1990’s). They helped me to track the first documented
use of code to print the message “Hello, World!” Brian Kernighan remembered writing the
code for part of the I/O section of the BCPL manual. Martin Richards—who seems to have a
treasure trove of notes, old documents, etc.—found the manual and confirmed that this was
the original appearance of the program. The code was used for early testing of the C compiler
and made its way into Kernighan and Ritchie’s book. Later, it was one of the first programs
used to test Bjarne Stroustrup’s C++ compiler.
It became a standard for new programmers after it appeared in Kernighan and Ritchie, which
is probably the best selling introduction to programming of all time.

2 https://stackoverflow.com/questions/602237/where-does-hello-world-come-from
The fundamentals of C
Getting started

Discussion (i)

I Comments are enclosed between /* and */.


I All C programs are made up of one or more functions and they all have a special
function called main. It is special because the execution of any program starts at the
beginning of its main function. This example program consists of only one
function—the main function.
I The keyword void in main(void) indicates that this main expects no arguments. In
general, the arguments that a function expects are specified in a parenthesized
parameter list following the function name.
I A C function may return a value to its caller. The main function is like any other C
function, except that it is called by the environment in which the program is executed,
and it should return its completion status to the environment. The int before main in
the line int main(void) indicates that main returns an integer value. By convention,
a 0 is returned to indicate normal completion of the program, whereas a nonzero
return value indicates abnormal termination.
The fundamentals of C
Getting started

Discussion (ii)
I An opening brace { following a function name and its parameter list marks the
beginning of the function body, and the corresponding closing brace } marks the end.
A function body may contain variable declarations and statements. The main function
in this program contains two statements, but no variable declarations.
I The first statement printf("Hello World!\n"); is a call to the standard library
function printf. When a program uses a function that is not coded as part of the
program, the compiler searches through the standard library for the mission function. If
the compiler finds the function, it adds the necessary code for that function to the
compiled program. A function is called by following its name by a parenthesized list of
arguments. Here, printf has just one argument, the string "hello world\n". When
printf is supplied with a string as argument, it prints the characters between the
double quotation marks. The \n represents the newline character and it causes the
output to advance to the left margin on the next line. The semicolon ; terminates the
statement.
I The second statement return 0; returns a 0 to signal successful completion of the
program.
I Information relevant to the functions of the standard library is grouped into standard
header files, and a program calling such a function must include the appropriate
header file using the #include directive. Information about the printf function is
contained in the header file <stdio.h> and the first line of the program
#include <stdio.h> includes it in the program.
The fundamentals of C
Getting started

sphere.c

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

# define PI 3.14159265

int main ()
{
float radius , volume ;

printf ("Input the radius of the sphere : ");


scanf ("%f", & radius );
printf (" Surface area = %f\n", 4. * PI * radius * radius );
volume = 4. * PI * radius * radius * radius / 3.;
printf (" Volume of sphere = %f\n", volume );

return EXIT_SUCCESS ;
}
The fundamentals of C
Getting started

Discussion (i)
I This program uses two functions from the standard library: scanf and printf. The
program therefore includes the header file <stdio.h>.
I C provides a #define directive to define symbolic names for constants. The
preprocessor replaces every occurrence of the macro name PI in the program text
with a copy of the macro 3.14159265.
I All variables used in a function must be declared and they are usually declared at the
beginning of the function body. The declaration

float radius , volume ;

establishes the variables radius and volume to be of type float.


I A call to printf is of the form printf(control string, arg1, arg2, ...). The
control string governs the conversion, formatting, and printing of the arguments to
printf. It may consist of ordinary characters that are reproduced unchanged on the
standard output. For example, the control string in

printf ("Input the radius of the sphere : ");

comprises only the ordinary characters and hence

Input the radius of the sphere :

is displayed on the standard output.


The fundamentals of C
Getting started

Discussion (ii)
I The control string may also include conversion specifications that control the
conversion of successive arguments arg1, arg2, ... before they are printed. Each
conversion specification consists of the character % followed by a conversion control
character. The conversion control character f causes the float or double argument
to be converted to decimal notation with six digits after the decimal point.
I The scanf function is the input analogue of the printf function. A call to scanf is of
the form scanf(control string, arg1, arg2, ...). The control string contains
conversion specifications according to which the characters from the standard input
are interpreted and the results are assigned to successive arguments
arg1, arg2, .... A whitespace character in the control string causes whitespace
characters in the input to be discarded. The scanf function reads one data item from
the input corresponding to each argument other than the control string and returns as
function value the total number of arguments successfully read. It returns EOF when
the end of input is reached. Note that in this program the return value of scanf is
ignored (a better program would check that the input has been successful before
proceeding). Each argument other than the control string must be a pointer to the
variable where the result of input is to be stored. We discuss pointers further below, for
the moment just note that if x is a variable then &x is a pointer to x. Thus if we want the
result of input to be stored in radius then we must specify &radius as the argument
to scanf.
The fundamentals of C
Getting started

Discussion (iii)

I Assignment statements are of the form variable = expression;


I The basic C data types are
I char—character;
I int—integer;
I float—single precision floating-point number;
I double—double precision floating-point number.
I The qualifiers long and short can be used to change the bit width of some of the
types. The details of the bit widths are implementation dependent and are specified in
the implementation’s standard header files <limits.h> and <float.h>. The qualifiers
signed and unsigned may be applied to char, short int, int or long int.
unsigned variables can only have nonnegative values, while signed variables can
have both positive and negative values. By default, integers are considered signed.
I The commonly used conversion control characters are: f for float or double, d for
int, c for char, and s for string.
I The macro EXIT_SUCCESS for 0 is #defined in the header file <stdlib.h>. It is
generally a good idea to use macros and avoid magic constants with unclear
meanings.
The fundamentals of C
Flow control

quadratic equation.c (i)

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

int main ()
{
float a, b, c;
float discriminant , root , root1 , root2;

printf ("Input three coefficients .\n");


scanf ("%f %f %f", &a, &b, &c);
if (a == 0)
{
printf ("Not a quadratic equation .\n");
return EXIT_FAILURE ;
}

discriminant = b * b - 4. * a * c;
The fundamentals of C
Flow control

quadratic equation.c (ii)

if ( discriminant < 0.)


printf ("No real roots .\n");
else if ( discriminant == 0.)
{
root = -b / (2. * a);
printf ("Two identical roots: %f\n", root);
}
else /* discriminant > 0. */
{
root1 = (-b + sqrt( discriminant )) / (2. * a);
root2 = (-b - sqrt( discriminant )) / (2. * a);
printf ("Two distinct roots: %f %f\n", root1 , root2);
}

return EXIT_SUCCESS ;
}
The fundamentals of C
Flow control

Discussion (i)
I This program uses the function sqrt from the standard maths library. The program
therefore includes the header file <math.h>. If the above program is contained in the
file conditional.c then you can compile it with the maths library using the -lm option
as follows:
gcc -Wall -o conditional conditional .c -lm

I C has no boolean data type so boolean-valued expressions are of type int with 0
representing false and any nonzero value representing true.
I The six relational operators are < (less than), <= (less than or equal to), > (greater
than), >= (greater than or equal to), == (equal to), and != (not equal to).
I The three logical operators are && (and), || (or), and ! (not).
I There is no endif in C. When conditional statements are nested, each else is
associated with the closest previous else-less if. Thus
if ( condition1 ) if ( condition2 ) statementA else statementB

is interpreted as
if ( condition1 ) {if ( condition2 ) statementA else statementB }

To associate the else with the first if one must use braces:
if ( condition1 ) {if ( condition2 ) statementA } else statementB
The fundamentals of C
Flow control

Discussion (i)

I Indentation should be used to make the intended meaning clear.


I For example:

if ( condition1 )
{
if ( condition2 )
statementA
}
else
statementB

I Or:

if ( condition1 ) {
if ( condition2 )
statementA
} else
statementB

I Note that the indentation is ignored by the compiler.


The fundamentals of C
Flow control

Constant multiway conditional—switch

When each of the tests in a multiway if statement checks for a different value of the same
expression we can use a switch statement rather than an
if ... else if ... else if ... else ... statement.
The fundamentals of C
Flow control

calculator.c (i)
# include <stdio.h>
# include <stdlib .h>

int main ()
{
char operator ;
float operand1 , operand2 ;

printf ("Input operand1 operator operand2 .\n");


scanf ("%f %c %f", &operand1 , &operator , & operand2 );

switch ( operator )
{
case ’+’:
printf ("%f\n", operand1 + operand2 );
break;
case ’-’:
printf ("%f\n", operand1 - operand2 );
break;
case ’*’:
printf ("%f\n", operand1 * operand2 );
break;
case ’/’:
printf ("%f\n", operand1 / operand2 );
break;
default :
printf (" Invalid operator \n");
break;
}
The fundamentals of C
Flow control

calculator.c (ii)

return EXIT_SUCCESS ;
}
The fundamentals of C
Flow control

Discussion

The switch causes flow control to jump to the appropriate case. After statements in that
case are completed, the flow would ‘fall through’ to the next statement (i.e. the first
statement of the next case) if the break were not there.
The fundamentals of C
Flow control

while loop.c

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

int main ()
{
int i, n;
float sum;

printf ("Input number of summands : ");


scanf ("%d", &n); /* read n */
i = 1; /* initialize the loop control variable i */
sum = 0.; /* initialize the accumulator sum */

while (i <= n) /* iterate n times */


{
sum += 1. / i; /* add the ith term to the sum */
i++; /* increment the loop control variable i */
}

printf ("sum = %f\n", sum); /* print sum */

return EXIT_SUCCESS ;
}
The fundamentals of C
Flow control

Discussion

I There is no endwhile. Braces { and } are used to give the scope of the while.
I x += y is short for x = x + y. Similarly -=, *=, and /=.
I i++ is short for i = i + 1 (increment).
I i-- is short for i = i - 1 (decrement).
The fundamentals of C
Flow control

for loop.c

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

int main ()
{
int i, n;
float sum;

printf ("Input number of summands : ");


scanf ("%d", &n);

for (i = 1, sum = 0; i <= n; i++)


sum += 1. / i;

printf ("sum = %f\n", sum);

return EXIT_SUCCESS ;
}
The fundamentals of C
Flow control

Discussion

The general form of the for statement is

for ( expression1 ; expression2 ; expression3 )


statement ;

where instead of a single statement there may be a block, and is equivalent to

expression1 ;
while ( expression2 ) {
statement ;
expression3 ;
}
The fundamentals of C
Functions

Call by value

C functions use call by value rather than call by reference as illustrated in the following
simple program.
The fundamentals of C
Functions

call by value.c

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

int main ()
{
int i = 1, j = 2;
void exchange (int , int);

printf ("main :\ti = %d\tj = %d\n", i, j);


exchange (i, j);
printf ("main :\ti = %d\tj = %d\n", i, j);

return EXIT_SUCCESS ;
}

void exchange (int i, int j)


{
int t;

t = i; i = j; j = t;
printf (" exchange :\ti = %d\tj = %d\n", i, j);
}
The fundamentals of C
Functions

Output

The following output is produced:

main : i = 1 j = 2
exchange : i = 2 j = 1
main : i = 1 j = 2
The fundamentals of C
Functions

A multi-function program spread between files

I A C program is usually made up of many small functions, each performing a particular


task, rather than one large main function.
I A program is usually kept in more than one source file, each file consisting of closely
related functions.
I Source files may be compiled separately and loaded together, possibly along with
previously compiled library functions.
The fundamentals of C
Functions

sphere.h

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

# define PI 3.14159265

float get_value (void);


void put_value ( float x);
float surface_area ( float r);
float volume ( float r);
The fundamentals of C
Functions

main.c

# include " sphere .h"

int main(void)
{
float radius , vol;

printf ("Input the radius of the sphere : ");


radius = get_value ();
printf (" Surface area = ");
put_value ( surface_area ( radius ));
vol = volume ( radius );
printf (" Volume of sphere = ");
put_value (vol);

return EXIT_SUCCESS ;
}
The fundamentals of C
Functions

simple io.c

# include " sphere .h"

float get_value (void)


{
float x;

scanf ("%f", &x);


return x;
}

void put_value ( float x)


{
printf ("%f\n", x);
}
The fundamentals of C
Functions

geometry.c

# include " sphere .h"

static float my_pow ( float x, int n);

float surface_area ( float r)


{
return 4. * PI * my_pow (r, 2);
}

float volume ( float r)


{
return 4. * PI * my_pow (r, 3) / 3.;
}

static float my_pow ( float x, int n)


{
if (n < 0)
return 1. / my_pow (x, -n);
else if (n == 0)
return 1;
else
return x * my_pow (x, n - 1);
}
The fundamentals of C
Functions

Discussion
I The header file "sphere.h" contains all the prototypes (signatures) of the public
functions used in the program. These prototypes are used by the compiler to type
check separately compiled files. Although it is not strictly necessary to include these
prototypes in files which do not call any of the functions (e.g. simple_io.c does not
contain any calls to these functions) it is a good idea to include them to force the
compiler to check consistency between the prototype and the actual definition of those
functions defined in the file.
I The function mypow is static which means that it is private to the file containing it.
Note that its prototype does not appear in "sphere.h".
I A function call can appear anywhere that an expression of the same type could
appear. For example, in

put_value ( surface_area ( radius ));

a function call appears as an argument to a function, and in

vol = volume ( radius );

a function call appears as the right hand side of an assignment statement.


I Functions may be recursive. For example, look at mypow.
The fundamentals of C
Functions

Compilation

I The files in the program can be compiled separately and then linked together. This
means that people could be working independently on the separate files as part of a
project and that if a small change is required in one file the whole of the program need
not be recompiled.
I You can compile and run the program as follows:

$ gcc -Wall -c main.c


$ gcc -Wall -c simple_io .c
$ gcc -Wall -c geometry .c
$ gcc -Wall -o myexe main.o simple_io .o geometry .o
$ myexe
Input the radius of the sphere : 2<cr >
surface area = 50.265482
volume of sphere = 33.510322
$

I Successful compilation of the files main.c, simple_io.c, and geometry.c produces


the files main.o, simple_io.o, and geometry.o. These .o files are then linked to
produce the executable myexe.
The fundamentals of C
Arrays

student scores.c

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

# define STUDENTS 10

int main ()
{
int i;
int score[ STUDENTS ];
float sum , average ;

printf ("Input %d scores .\n", STUDENTS );

for (i = 0; i < STUDENTS ; i++)


scanf("%d", &score[i]);

for (sum = 0., i = 0; i < STUDENTS ; i++)


sum += score[i];

average = sum / STUDENTS ;

printf (" Average score = %f\n", average );

return EXIT_SUCCESS ;
}
The fundamentals of C
Arrays

Discussion
I In C array subscripts start at 0 rather than 1 and cannot be negative.
I Any integral expression can be used as a subscript. For example, score[3*i+2] is a
valid reference to an element of score.
I C does not automatically check that subscripts lie within the array bounds.
I Elements of an array can be assigned initial values by following the array definition
with a list of initializers enclosed in braces and separated by commas. For example,

char letter [3] = {’a’, ’b’, ’c’}

defines the array letter to contain three character elements and initializes letter[0]
to ’a’, letter[1] to ’b’, and letter[2] to ’c’.
I In C, an array name by itself (without an index) is a constant whose value is the
address of the start of the array. Since it is a constant, it cannot appear as the
left-hand side of an assignment statement. This means that we cannot copy one array
to another by a single assignment. For example, given the definition and initialization
of letter above the following is illegal:

char another_letter [3];


another_letter = letter ; /* illegal */

I We would need to use a for loop to assign the value of each individual array element
to the new array.
The fundamentals of C
Arrays

sort.c (i)
# include <stdio.h>
# include <stdlib .h>

# define MAXSIZE 100

int readl (int arr [], int length );


void writel (int arr [], int last);
void bsort (int arr [], int last);

int main ()
{
int list[ MAXSIZE ], total_elements ;

total_elements = readl(list , MAXSIZE );


bsort (list , total_elements );
writel (list , total_elements );
return EXIT_SUCCESS ;
}

int readl (int arr [], int length )


{
int i;

printf ("Input integer elements (EOF to finish , at most %d).\n", length );


for (i = 0; i < length && scanf("%d", &arr[i]); i++)
;
return i;
}
The fundamentals of C
Arrays

sort.c (ii)
void bsort (int arr [], int last)
{
int i, not_done ;

do
{
not_done = 0;
/* Scan and interchange as needed . */
for (i = 1; i < last; i++)
if (arr[i - 1] < arr[i])
{
int t;
t = arr[i];
arr[i] = arr[i - 1];
arr[i - 1] = t;
not_done ++;
}
last --;
} while ( not_done );
}

void writel (int arr [], int last)


{
int i;

for (i = 0; i < last; i++)


printf ("%d\n", arr[i]);
}
The fundamentals of C
Arrays

Discussion

I To pass an entire array to a function, just the name of the array, without any subscripts
or brackets, is specified as the argument in the function call.
I Note that bsort changes the values of array elements. This does not contradict the
call by value parameter passing because when an array is passed as an argument, it
is only the address of the beginning of the array that is passed; the elements of the
array are not copied into a parameter array. Any references to the parameter array
inside the called function refer to the appropriate elements of the argument array.
The fundamentals of C
Arrays

array caveat.c

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

# define FLOAT_ARRAY_ELEMENT_COUNT (x) ( sizeof (x) / sizeof ( float))

void my_func (float a[]);

int main ()
{
float my_array [5];
printf ("Size of my_array outside my_func : %d\n", FLOAT_ARRAY_ELEMENT_COUNT (
,→ my_array ));
my_func ( my_array );
return EXIT_SUCCESS ;
}

void my_func (float a[])


{
printf ("Size of a inside my_func : %d\n", FLOAT_ARRAY_ELEMENT_COUNT (a));
}
The fundamentals of C
Arrays

Discussion

I There is no ‘built-in’ way to determine the length of an array inside a function.


I However you pass my_array, sizeof(a) will always return the pointer size.
I The best way is to pass the number of elements as a separate argument.
I Alternatively, you could have a special value like 0 or -1 that indicates the end (like it is
0 in strings, which are just char[]). But then of course the ‘logical’ array size would be
sizeof(a) / sizeof(float) - 1.
The fundamentals of C
Pointers

Pointers

I Memory can be visualized as an ordered sequence of consecutively numbered


storage locations.
I A data item is stored in memory in one or more adjacent storage locations depending
on its type.
I The address of a data item is the address of its first storage location.
I This address can be stored in another data item and manipulated in a program.
I The address of a data item is called a pointer to the data item and a variable that
holds an address is called a pointer variable.
I There is a special value called NULL which a pointer variable can have. A pointer
variable with value NULL does not point to any location.
The fundamentals of C
Pointers

pointers.c

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

int main ()
{
int i, j = 1;
int *jp1 , *jp2 = &j; /* jp2 points to j */

jp1 = jp2; /* jp1 also points to j */


i = *jp1; /* i gets the value of j */
*jp2 = *jp1 + i; /* i is added to j */
printf ("i = %d, j = %d, *jp1 = %d, *jp2 = %d\n",
i, j, *jp1 , *jp2);
return EXIT_SUCCESS ;
}
The fundamentals of C
Pointers

Discussion

I This program prints:

i = 1, j = 2, *jp1 = 2, *jp2 = 2

I The operator &, when applied to a variable, yields its address (pointer to the variable).
I The operator *, when applied to an address (pointer), yields the value at that address.
I For each type of object that can be declared in C, a corresponding type of pointer can
be declared. To indicate that a variable contains a pointer to a specified type of object,
rather than the object itself, an asterisk is included before the name of the object in the
type declaration. Thus int *jp1 declares jp1 to be of type ‘pointer to int’. The
declaration allocates space for the named pointer variable but not for what it points to.
The fundamentals of C
Pointers

exchange.c

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

int main ()
{
int i = 1, j = 2;

void exchange (int*, int *);

printf ("main :\ti = %d\tj = %d\n", i, j);


exchange (&i, &j);
printf ("main :\ti = %d\tj = %d\n", i, j);

return EXIT_SUCCESS ;
}

void exchange (int* ip , int* jp)


{
int t;

t = *ip , *ip = *jp , *jp = t;


printf (" exchange :\ti = %d\tj = %d\n", *ip , *jp);
}
The fundamentals of C
Pointers

Discussion

I When a pointer is passed as an argument to a function, the pointer itself is copied but
the object pointed to is not copied. Hence any change made to the pointer parameter
by the called function does not affect the pointer supplied as argument to the function.
This is consistent with call by value parameter passing. However, using the pointer
supplied as the argument, the called function can access and modify the object
pointed to by the pointer in the calling function. This is known as call by reference.
I The following output is produced:

main : i = 1 j = 2
exchange : i = 2 j = 1
main : i = 2 j = 1
The fundamentals of C
Pointers

command line args.c

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

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


{
int i;

for (i = 1; i < argc; i++)


printf ("%s ", argv[i]);
printf ("\n");

return EXIT_SUCCESS ;
}
The fundamentals of C
Pointers

Discussion (i)

I All C programs define a function main that designates the entry point of the program
and is invoked by the environment in which the program is executed. In the programs
considered so far, main did not take any arguments. However, main can be defined
with formal parameters so that the program may accept command-line arguments, that
is, arguments that are specified when the program is executed. The given program
simply echoes its command-line arguments.
I argc is the count of the number of command-line arguments. It is at least one since
the first argument is the name of the program itself.
I In C, a string is a null-terminated array of characters. This is, an array of characters
ending in 0.
I argv is an array of pointers to character strings representing the arguments. By
convention, argv[0] points to a string which is the name of the program, argv[i], for
i = 1, 2, ..., argc - 1, points to the ith argument, and argv[argc] is NULL.
The fundamentals of C
Pointers

Discussion (ii)

I Whitespaces are used to delimit the command-line arguments. If an argument


contains whitespaces it must be placed within quotation marks. For example, if the
above program is compiled to the executable myecho then for the invokation

$ myecho happy birthday


happy birthday

I argc is 3 and argv is an array of three pointers pointing to strings "myecho",


"happy", and "birthday", respectively.
I Whereas for the invokation

$ myecho "happy birthday "


happy birthday

argc is 2 and argv is an array of two pointers pointing to the strings "myecho" and
"happy birthday", respectively.
The fundamentals of C
Dynamic memory allocation

Dynamic memory allocation

In many programs, the number of objects to be processed by the program is not known in
advance. C provides a collection of dynamic memory management functions that enable
storage to be allocated as needed and released when no longer required.
The fundamentals of C
Dynamic memory allocation

dynamic memory allocation.c (i)

# include <stdio.h>
# include <stdlib .h> /* contains EXIT_SUCCESS , EXIT_FAILURE and prototypes of strcpy
,→ and strcat */
# include <string .h> /* contains prototypes of strcpy and strcat */

# define STRING "Happy Birthday "

int main ()
{
char* cp;

cp = (char *) malloc ( sizeof ( STRING ));


if (cp == NULL)
{
printf ("No memory \n");
return EXIT_FAILURE ;
}
strcpy (cp , STRING );
The fundamentals of C
Dynamic memory allocation

dynamic memory allocation.c (ii)

cp = (char *) realloc (cp , sizeof ( STRING ) + 1);


if (cp == NULL)
{
printf ("No memory \n");
return EXIT_FAILURE ;
}

printf ("%s\n", strcat (cp , "!"));

free(cp);

return EXIT_SUCCESS ;
}
The fundamentals of C
Dynamic memory allocation

Discussion
I The sizeof operator is a unary operator that is used to obtain the size of a type or
data object. If the operand to sizeof is an n-element array of some type, the result of
sizeof is n times the result of sizeof applied to that type. A string constant is a
null-terminated array of characters so sizeof applied to a string constant yields the
number of characters in the string constant including the trailing 0. Note that sizeof is
computed at compile time using the type rather than the value of its input.
I The function malloc is used to obtain storage for an object. The input to malloc is the
size of the storage to be allocated. It returns a pointer to the allocated storage and
NULL if it is not possible to allocate the storage requested.
I The function realloc changes the size of the object pointed to by its first argument to
its second argument. It returns a pointer to the new storage and NULL if it is not
possible to resize the object. The new size may be larger or smaller than the original
size. If the new size is larger, the original contents are preserved and the remaining
space is uninitiated; if smaller, the contents are unchanged up to the new size.
I The function free deallocates the storage pointed to by its input. If its input is a NULL
pointer then it does nothing.
I strcpy copies the string given as its second argument to its first argument (including
the 0 and returns the copied string.
I strcat concatenates the string given as its second argument to its first argument and
returns the concatenated string.
The fundamentals of C
Structures

Structures

Arrays provide the facility for grouping related data items of the same type into a single
object. Structures serve the same purpose for related data items of different types.
The fundamentals of C
Structures

structures.c (i)

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

struct point
{
float x, y;
};

struct circle
{
float r;
struct point o;
};

float sqr( float x)


{
return x * x;
}

int contains ( struct circle c, struct point p)


{
return sqr(c.o.x - p.x) + sqr(c.o.y - p.y) <= sqr(c.r);
}
The fundamentals of C
Structures

structures.c (ii)

int main ()
{
struct circle mycircle = { 2, {1, 1} };
struct point mypoint = { 2, 2 };

printf ("The circle with centre (%f, %f)\n", mycircle .o.x, mycircle .o.y);
printf ("and radius %f\n", mycircle .r);
if ( contains (mycircle , mypoint ))
printf (" contains ");
else
printf ("does not contain ");
printf ("the point (%f, %f).\n", mypoint .x, mypoint .y);

return EXIT_SUCCESS ;
}
The fundamentals of C
Structures

Discussion (i)

I A structure is a collection of variable declarations grouped together under a single


name called the structure tag. point and circle are structure tags.
I The data items that make up a structure are called its fields. x and y are fields of the
structure point and r and o are fields of the structure circle.
I A structure definition like

struct circle
{
float r;
struct point o;
}

defines a new type but does not declare a variable of that type.
I Variable declarations for the new type are similar to variable declarations for simple
types. Note how the fields are initialized.

struct circle mycircle = {2, {1, 1}};

I Fields of a structure are accessed using the dot operator. For example, mycircle.r is
the r field of the variable mycircle of type circle.
The fundamentals of C
Structures

Discussion (ii)

I Structures can be nested, look at circle, but a structure cannot be nested within
itself. For example, the following is illegal:

struct person
{
int age;
struct person mother ; /* illegal */
struct person father ; /* illegal */
}

I However, a structure can contain a pointer to itself. This is very useful in building
recursive data types like linked lists and trees:

struct node
{
int data;
struct node *next; /* legal and very useful ! */
}
The fundamentals of C
Structures

Discussion (iii)
I Functions can take structures as arguments and return structures as results. When a
structure is provided as an argument, the entire structure is copied to the called
function, and changes to field variables in the structure argument are not reflected in
the corresponding structure variable in the called function. That is structures are
passed by value. Note that this is not the same as the case for arrays in which the
array name represents the address of the start of the array and the array is not copied
to the called function.
I To save the overhead in copying structures to called functions and to allow functions to
change structure arguments we can pass a reference to a structure as argument to a
function. For example, contains could be written as

int contains ( struct circle *c, struct point *p)


{
return sqr(c->o.x - p->x) + sqr(c->o.y - p->y) <= sqr(c->r);
}

and called as

contains (& mycircle , & mypoint )

I Note that in general ptr->x is shorthand for (*ptr).x so that c->r is shorthand for
(*c).r and c->o.x is shorthand for (*c).o.x.
The fundamentals of C
Unions

Unions

A union is a construct that allows different types of data items to share the same block of
memory. The compiler automatically allocates sufficient space to hold the largest data item
in the union. However, it is the programmer’s responsibility to keep track of what is currently
stored in the union. The syntax for defining and accessing unions is similar to that for
structures but with the keyword union in place of struct.
The fundamentals of C
Unions

unions.c (i)
# include <stdio.h>
# include <stdlib .h>

# define PI 3.14159265
# define CIRCLE 1
# define RECTANGLE 2

struct point
{
float x, y;
};

struct circle
{
float r; /* radius */
struct point o; /* centre */
};

struct rectangle
{
struct point tr; /* top right corner of rectangle */
struct point bl; /* bottom left corner of rectangle */
};

union shape
{
struct circle c;
struct rectangle r;
};
The fundamentals of C
Unions

unions.c (ii)
float area( union shape s, int which_shape )
{
switch ( which_shape )
{
case CIRCLE :
return PI * s.c.r * s.c.r;
case RECTANGLE :
return (s.r.tr.x - s.r.bl.x) * (s.r.tr.y - s.r.bl.y);
}
}

int main(void)
{
union shape myshape ;

myshape .c.r = 2;
myshape .c.o.x = 1;
myshape .c.o.y = 1;
printf ("The area of the circle is %f\n", area(myshape , CIRCLE ));

myshape .r.tr.x = 1;
myshape .r.tr.y = 1;
myshape .r.bl.x = -1;
myshape .r.bl.y = -1;
printf ("The area of the rectangle is %f\n", area(myshape , RECTANGLE ));

return EXIT_SUCCESS ;
}
The fundamentals of C
The preprocessor

The preprocessor

I The preprocessor is a program that processes the source text of a C program before
the compiler.
I It has three main functions:
I file inclusion—the insertion of the text of a file into the current file;
I macro replacement—the replacement of one string by another;
I conditional inclusion—the selective inclusion and exclusion of portions of source text on
the basis of a computed condition.
I Actions of the preprocessor are controlled by special directives placed in the source
file.
I A preprocessor directive begins with the character # on a fresh line, and is terminated
by the newline character unless continued on the next line by placing a backslash at
the end of the line.
The fundamentals of C
The preprocessor

File inclusion
I A large program is developed by grouping logically related functions into separate files.
I Symbolic constants and data types common to more than one file and the external
declarations for the shared variables are then collected in one or more files, called
header files, and included in files that need them using the #include directive.
I This approach ensures that all the source files will be supplied with the same
definitions and variable declarations.
I You may also collect useful macro definitions that may be required in different
programs in one or more files and then include them in your programs as needed.
I The #include directive has two forms:

# include <filename >


# include " filename "

I Both forms instruct the preprocessor to replace the line containing the #include
directive by the contents of the filename.
I On UNIX systems, for a directive of the first form, the named file is searched for in
some standard places (for example /usr/include), and for a directive of the second
form it is searched for first in the directory in which the file containing the #include
directive was found and if that search fails it is searched for in standard places.
The fundamentals of C
The preprocessor

Macro replacement (i)


I We have already seen the #define directive for defining simple macros:

# define PI 3.14159265

I The preprocessor replaces every occurrence of PI in the program text with


3.14159265 except within comments or string constants.
I The #define directive can also be used for defining parameterized macros. The
general form for defining a parameterized macro is

# define macro_name (param1 , param2 , ...) sequence_of_tokens

I The macro name is followed by a comma-separated list of formal parameters enclosed


within a pair of parentheses. The sequence of tokens following the formal parameters
to the end of the line define the macro body.
I Examples are:

# define READ(I) scanf ("%d", &I)


# define CONVERT (I) printf (" decimal %d \
= octal %o\n", I, I)

I The preprocessor performs two levels of replacement on parameterized macros: first


the formal parameters in the body of the macro are replaced by the actual arguments,
and then the resulting macro body is substituted for the macro call.
The fundamentals of C
The preprocessor

Macro replacement (ii)


I Thus the preprocessor will replace the following statements

READ(n);
CONVERT (n);

by

scanf ("%d", &n);


printf (" decimal %d = octal %o\n", n, n);

I Arguments in a macro call can be any token sequence, including commas, provided
that the sequence is bracketed within a pair of parentheses so that given the definitions

# define PROTOTYPE_KR (FUNCTION , PARAMS ) FUNCTION ()


# define PROTOTYPE_ANSI (FUNCTION , PARAMS ) FUNCTION PARAMS

the preprocessor will replace the following

PROTOTYPE_KR (int contains , ( struct circle c, struct point p));


PROTOTYPE_ANSI (int contains , ( struct circle c, struct point p));

by

int contains ();


int contains ( struct circle c, struct point p);
The fundamentals of C
The preprocessor

Conditional inclusion (i)

I Conditional inclusion allows selective inclusion of lines of source text on the basis of a
computed condition. Conditional inclusion is performed using the preprocessor
directives #if, #ifdef, #ifndef, #elif, #else, #endif.
I #ifdef stands for ‘if defined’, #ifndef stands for ‘if not defined’, and #elif is short
for ‘else if’.
I For example, if DEBUG has been defined as

# define DEBUG anything

then the lines

# ifdef DEBUG
printf (" iteration %d : x = %f\n", i, x);
# endif

would result in the line

printf (" iteration %d : x = %f\n", i, x);

being included in the code whereas if DEBUG has not been defined then no line will be
included in the code.
The fundamentals of C
The preprocessor

Conditional inclusion (ii)

I Instead of embedding #ifdef DEBUG directives all over the code when you require
many debugging statements in a program, you may define a PRINT macro as

# ifdef DEBUG
# define PRINT(ARG) printf ARG
#else
# define PRINT(ARG)
# endif

then

PRINT ((" iteration %d : x = %f\n", i, x));

will expand to either

printf (" iteration %d : x = %f\n", i, x);

or nothing depending on whether or not DEBUG is defined.


The fundamentals of C
The preprocessor

Conditional inclusion (iii)

I Similarly, if we define a PROTOTYPE macro as

# ifdef ANSI
# define PROTOTYPE (FUNCTION , PARAMS ) FUNCTION PARAMS
#else
# define PROTOTYPE (FUNCTION , PARAMS ) FUNCTION ()
# endif

then

PROTOTYPE (int contains , ( struct circle c, struct point p));

will expand to either

int contains ( struct circle c, struct point p);

or

int contains ();

depending on whether or not ANSI is defined.


The fundamentals of C
The Makefile

Dependencies
I Recall that the multi-function program spread between files shown above was
compiled to produce the executable myexe as follows:

$ gcc -Wall -c main.c


$ gcc -Wall -c simple_io .c
$ gcc -Wall -c geometry .c
$ gcc -Wall -o myexe main.o simple_io .o geometry .o

I Recall also that the header file sphere.h was included in main.c, simple_io.c, and
geometry.c.
I Now, if a change is made to simple_io.c then to build an up-to-date executable we
would need to recompile simple_io.c to produce an up-to-date simple_io.o and
relink to produce an up-to-date executable myexe but we would not need to recompile
main.c or geometry.c.
$ gcc -Wall -c simple_io .c
$ gcc -Wall -o myexe main.o simple_io .o geometry .o

I On the other hand, if a change is made to the header file sphere.h then all three .c
files need to be recompiled and we need to relink.
I The executable myexe is said to depend on the object files main.o, simple_io.o,
and geometry.o. Also each .o file depends on both the corresponding .c file and
the header file sphere.h.
The fundamentals of C
The Makefile

The Makefile

I The Makefile is a file which records these dependencies and uses them together with
last modified times of the files to build a new executable by doing the minimum
necessary recompilation.
I A general entry in the makefile is of the form

<file -produced > : <file -used -1> <file -used -2> ... <file -used -n>
action to build <file -produced >

I When the makefile is run, the last modified times of the files are checked and if any of
the <file-used-i> are younger than <file-produced> then the action to build an
up-to-date <file-produced> is performed, otherwise nothing is done.
I The line giving the action must begin with a tab.
The fundamentals of C
The Makefile

A simple Makefile

# A simple Makefile for the multi - function program spread


# between files shown above.
myexe : main.o simple_io .o geometry .o
gcc -Wall -o myexe main.o simple_io .o geometry .o
main.o : main.c sphere .h
gcc -Wall -c main.c
simple_io .o : simple_io .c sphere .h
gcc -Wall -c simple_io .c
geometry .o : geometry .c sphere .h
gcc -Wall -c geometry .c
The fundamentals of C
The Makefile

A simple Makefile

Dependencies can also be given as separate lines with no corresponding action. For
example, the previous makefile is equivalent to

# A simple Makefile for the multi - function program spread


# between files shown above.
myexe : main.o simple_io .o geometry .o
gcc -Wall -o myexe main.o simple_io .o geometry .o
main.o : main.c
gcc -Wall -c main.c
simple_io .o : simple_io .c
gcc -Wall -c simple_io .c
geometry .o : geometry .c
gcc -Wall -c geometry .c
main.o simple_io .o geometry .o : sphere .h
The fundamentals of C
The Makefile

Macro definitions

I Notice that there is a considerable amount of repetition in the makefile.


I Notice also that if we wished to change the compiler options (for example to add the -g
option for debugging) then we would have to make the same change in many places.
I To improve matters we can use a macro definition to assign a symbol to some text. A
macro definition is a line in the makefile of the form

NAME = sequence_of_tokens

I In the makefile, expressions of the form $(NAME) or ${NAME} are replaced by


sequence_of_tokens.
The fundamentals of C
The Makefile

A not so simple makefile

# A not so simple Makefile for the multi - function program spread


# between files shown above.
OFILES = main.o simple_io .o geometry .o
CC = gcc
CFLAGS = -Wall
myexe : $( OFILES )
$(CC) $( CFLAGS ) -o myexe $( OFILES )
main.o : main.c
$(CC) $( CFLAGS ) -c main.c
simple_io .o : simple_io .c
$(CC) $( CFLAGS ) -c simple_io .c
geometry .o : geometry .c
$(CC) $( CFLAGS ) -c geometry .c
$( OFILES ) : sphere .h
The fundamentals of C
The Makefile

The command line


I Macros can be defined on the make command line. For example

make " CFLAGS = -Wall -g"

would redefine the compiler options to include -g for debugging.


I Dependencies and actions of the following forms

main.o : main.c
$(CC) $( CFLAGS ) -c main.c
simple_io .o : simple_io .c
$(CC) $( CFLAGS ) -c simple_io .c
geometry .o : geometry .c
$(CC) $( CFLAGS ) -c geometry .c

are so standard that many systems automatically include a rule to generate them.
Hence our file can be shortened to

# A not so simple Makefile for the multi - function program spread


# between files shown above.
OFILES = main.o simple_io .o geometry .o
CC = gcc
CFLAGS = -Wall
myexe : $( OFILES )
$(CC) $( CFLAGS ) -o myexe $( OFILES )
$( OFILES ) : sphere .h
The fundamentals of C
What to do when things go wrong

What to do when things go wrong

I When your program crashes or gives output that you do not understand, you can use a
debugger to help you to find out what is going on.
I We recommend that you produce debugging information (option -g for GCC).
I You can then use a debugger such as gdb on Linux or the integrated debugger in
Visual Studio.
The fundamentals of C
Writing good C code

Some advice

Consider the following advice from [Zhi17]:


I Always separate program logic from input and output operations. This will allow for
better code reuse. If a function performs actions on data and outputs messages at the
same time, you won’t be able to reuse its logic in another situation (e.g., it can output
messages to an application with a graphical user interface, and in another case you
might want to use it on a remote server).
I Always comment your code in plain English.
I Name your variables based on their meaning for the program. It is very hard to deduce
the meaning of variables with meaningless names, such as aaa.
I Remember to put const wherever you can.
I Use appropriate types for indexing.
The fundamentals of C
Writing good C code

An example

Consider the following program which implements array summation:

# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};

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


int i;
int sum;
for (i = 0; i < 5; i++)
sum = sum + array[i];
printf ("The sum is: %d\n", sum);
return 0;
}

Before we start polishing the code, can you spot a bug in this program?
The fundamentals of C
Writing good C code

A bug

The variable sum is not initialized. Local variables in C are not initialized by default, so you
have to do it by hand.
The fundamentals of C
Writing good C code

A correction

# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};

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


int i;
int sum = 0;
for (i = 0; i < 5; i++)
sum = sum + array[i];
printf ("The sum is: %d\n", sum);
return 0;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

An issue with the code

I In its current form, the code is not reusable.


I Let’s extract a piece of logic into an array_sum function.
The fundamentals of C
Writing good C code

An improvement

# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};

void array_sum (void) {


int i;
int sum = 0;
for (i = 0; i < 5; i++)
sum = sum + array[i];
printf ("The sum is: %d\n", sum);
}

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


array_sum ();
return 0;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

An issue with the code

I We are using a magic number or magic constant 5 in the code.


I The use of unnamed magic numbers in code obscures the developers’ intent in
choosing that number, increases opportunities for subtle errors (e.g. is every digit
correct in 3.14159265358979323846 and is this equal to 3.14159?) and makes it more
difficult for the program to be adapted and extended in the future.
I In our case, every time we change the array we have to change this number as well,
so we probably want to calculate it dynamically.
The fundamentals of C
Writing good C code

An improvement

# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};

void array_sum (void) {


int i;
int sum = 0;
for (i = 0; i < sizeof (array) / 4; i++)
sum = sum + array[i];
printf ("The sum is: %d\n", sum);
}

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


array_sum ();
return 0;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

An issue with the code

I But why are we dividing the array size by 4?


I The size of int varies depending on the architecture, so we have to calculate it too (at
compile time).
The fundamentals of C
Writing good C code

An improvement

# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};

void array_sum (void) {


int i;
int sum = 0;
for (i = 0; i < sizeof (array) / sizeof (int); i++)
sum = sum + array[i];
printf ("The sum is: %d\n", sum);
}

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


array_sum ();
return 0;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

An issue with the code

I We immediately face a problem: sizeof returns a number of type size_t, not int.
I So, we have to change the type of i.
The fundamentals of C
Writing good C code

An improvement

# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};

void array_sum (void) {


size_t i;
int sum = 0;
for (i = 0; i < sizeof (array) / sizeof (int); i++)
sum = sum + array[i];
printf ("The sum is: %d\n", sum);
}

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


array_sum ();
return 0;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

An issue with the code

I Right now, array_sum works only on statically defined arrays, because they are the
only ones whose size can be calculated by sizeof.
I Next we want to add enough parameters to array_sum so it would be able to sum any
array.
I You cannot add only a pointer to an array, because the array size is unknown by
default, so you give it two parameters: the array itself and the number of elements in
the array.
The fundamentals of C
Writing good C code

An improvement

# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};

void array_sum (int* array , size_t count) {


size_t i;
int sum = 0;
for (i = 0; i < count; i++)
sum = sum + array[i];
printf ("The sum is: %d\n", sum);
}

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


array_sum (array , sizeof (array) / sizeof (int));
return 0;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

An issue with the code

I This code is much better but it still breaks the rule of not mixing input/output and logic.
I You cannot use array_sum anywhere in graphical programs, you also can do nothing
with the result.
I We are going to get rid of the output in the summation function and make it return its
result.
The fundamentals of C
Writing good C code

An improvement

# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};

void array_sum (int* array , size_t count) {


size_t i;
int sum = 0;
for (i = 0; i < count; i++)
sum = sum + array[i];
return sum;
}

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


printf (
"The sum is: %d\n",
array_sum (array , sizeof (array) / sizeof (int))
);
return 0;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

An issue with the code

I We have to think about adding const qualifiers.


I The most important place is function arguments of pointer types.
I We really want to declare that array_sum will never change the array that its argument
is pointing at.
I We can also protect the global array itself from being changed by adding a const
qualifier.
I Remember that if we make the global array itself constant but will not mark array in
the argument list as such, we would not be able to pass array to array_sum, because
there are no guarantees that array_sum will not alter the data that its argument is
pointing at.
The fundamentals of C
Writing good C code

An improvement

# include <stdio.h>

const int array [] = {1, 2, 3, 4, 5};

void array_sum ( const int* array , size_t count) {


size_t i;
int sum = 0;
for (i = 0; i < count; i++)
sum = sum + array[i];
return sum;
}

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


printf (
"The sum is: %d\n",
array_sum (array , sizeof (array) / sizeof (int))
);
return 0;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

Other issues with the code

I There is another magic constant in the code, namely 0.


I We can replace it with EXIT_SUCCESS from stdlib.h.
I We can also replace sum = sum + array[i] with the terser but equally clear
sum += array[i].
The fundamentals of C
Writing good C code

An improvement

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

const int array [] = {1, 2, 3, 4, 5};

void array_sum ( const int* array , size_t count) {


size_t i;
int sum = 0;
for (i = 0; i < count; i++)
sum += array[i];
return sum;
}

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


printf (
"The sum is: %d\n",
array_sum (array , sizeof (array) / sizeof (int))
);
return EXIT_SUCCESS ;
}

Can you spot other issues with this code?


The fundamentals of C
Writing good C code

Remaining issues with the code

I Can the pointer array be NULL? If so, how do we signal this without dereferencing a
NULL pointer, which will probably result in a crash?
I Can sum overflow?
The fundamentals of C
Bibliography

Igor Zhirkov.
Low-Level Programming: C, Assembly, and Program Execution on Intelr64
Architecture.
Apress, 2017.

You might also like