Professional Documents
Culture Documents
The fundamentals of C
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
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
Clang
/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
hello world.c
# 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
2 https://stackoverflow.com/questions/602237/where-does-hello-world-come-from
The fundamentals of C
Getting started
Discussion (i)
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 ;
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
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)
# include <math.h>
# include <stdio.h>
# include <stdlib .h>
int main ()
{
float a, b, c;
float discriminant , root , root1 , root2;
discriminant = b * b - 4. * a * c;
The fundamentals of C
Flow control
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)
if ( condition1 )
{
if ( condition2 )
statementA
}
else
statementB
I Or:
if ( condition1 ) {
if ( condition2 )
statementA
} else
statementB
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 ;
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;
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;
return EXIT_SUCCESS ;
}
The fundamentals of C
Flow control
Discussion
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);
return EXIT_SUCCESS ;
}
t = i; i = j; j = t;
printf (" exchange :\ti = %d\tj = %d\n", i, j);
}
The fundamentals of C
Functions
Output
main : i = 1 j = 2
exchange : i = 2 j = 1
main : i = 1 j = 2
The fundamentals of C
Functions
sphere.h
# include <stdio.h>
# include <stdlib .h>
# define PI 3.14159265
main.c
int main(void)
{
float radius , vol;
return EXIT_SUCCESS ;
}
The fundamentals of C
Functions
simple io.c
geometry.c
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
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:
student scores.c
# include <stdio.h>
# include <stdlib .h>
# define STUDENTS 10
int main ()
{
int i;
int score[ STUDENTS ];
float sum , 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,
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:
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>
int main ()
{
int list[ MAXSIZE ], total_elements ;
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 );
}
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>
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 ;
}
Discussion
Pointers
pointers.c
# include <stdio.h>
# include <stdlib .h>
int main ()
{
int i, j = 1;
int *jp1 , *jp2 = &j; /* jp2 points to j */
Discussion
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;
return EXIT_SUCCESS ;
}
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
# include <stdio.h>
# include <stdlib .h>
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)
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
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
# 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 */
int main ()
{
char* 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;
};
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)
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.
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
and called as
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:
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
# define PI 3.14159265
READ(n);
CONVERT (n);
by
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
by
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
# ifdef DEBUG
printf (" iteration %d : x = %f\n", i, x);
# endif
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
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
# ifdef ANSI
# define PROTOTYPE (FUNCTION , PARAMS ) FUNCTION PARAMS
#else
# define PROTOTYPE (FUNCTION , PARAMS ) FUNCTION ()
# endif
then
or
Dependencies
I Recall that the multi-function program spread between files shown above was
compiled to produce the executable myexe as follows:
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
Dependencies can also be given as separate lines with no corresponding action. For
example, the previous makefile is equivalent to
Macro definitions
NAME = sequence_of_tokens
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
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
An example
# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};
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};
An improvement
# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};
An improvement
# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};
An improvement
# include <stdio.h>
int array [] = {1, 2, 3, 4, 5};
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};
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};
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};
An improvement
# include <stdio.h>
An improvement
# include <stdio.h>
# include <stdlib .h>
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.