review of the c programming language (with some new material)
TRANSCRIPT
Review of the C Programming Language
(with some new material)
Last revised: Jan 7, 2021
If you know Javayou know most of C
Common Programming Language Features
• Comments
• Data Types
• Variable Declarations
• Expressions
• Flow of Control Statements
• Functions and Subroutines
• Macros and Preprocessor Commands
• I/O Statements
• Libraries
• External Tools (Compiler, debugger etc)
Comments in C
• comments are the most important feature in any programming language because comments assist with debugging!!!
• Comments should precede any block of code or any code that might be difficult to understand. A comment should describe the intent of what you are trying to do.
• Write your comments BEFORE you write your code. Do not rely on the code itself to document what you are doing as the code may be incorrect
• Comments may also be used to temporarily remove a block of code from your program
/* This is a comment
it can span many lines */
//Single line comments
Primitive Data Types• Primitive data
types are built into the language
• They are scalar
(single valued)
variables
• data types are:
char, int, long, long
long, float, double,
long double, bool
and pointer
Ordinal Constants in C
• char : ‘A’, ‘AB’, ‘ABCD’
• int: -200, – 1000000L
– (unsigned) 127
– 0x80FA (hex),
– 007 (octal)
– 0 (false)
– non-zero (true)
* range of values depends on word size of the machine. Usually 4 bytes for a long int.
“ordinal” means countable – there is a next value.
Special Character Constants
• ‘\n’ - newline
• ‘\r’ - carriage return
• ‘\t’ - tab
• ‘\\’ - backslash
• ‘\’’ - quoted apostrophe
• ‘\”’ –quoted quote
• ‘\0’ – null character
• ‘\a’ – audible alert
• ‘\b’ - backspace
Pointers
• *(0xF00AA00)&myVariable
• Pointers are ordinal (countable) types
• A pointer is an address in memory
• a pointer points to the start of a block of memory
• sizeof pointer depends on the addressable range of the machine. (can have 2, 4, 6 or even 8 byte pointers)
Non-Ordinal Constants
• float : 12.5E-12. -0.5
• double: 12.5E+200
Always use doubles, not
floats.
Unicode Constants
• wchar_t greeting[ ]= L”Hello🌍”;
• wchar_t smiley=L’\x1f30d’
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
{
wchar_t greeting[ ] =L”Hello”;
wchar_t world=L’\x1F30D’;
setlocale(LC_ALL,”en_CA.UTF-8”);
fwprintf(stdout,L”%ls %lc\n”, greeting, world);
return 0;
}
Non-Primitive Data Types
• contain multiple values
• arrays
• structs
• unions
Declaring Scalar Variables
• short cake;
• int i;
• unsigned int value;
• long face;
• float icecream;
• double mint;
• char *myPointer;
• enum suit
{club, diamond, heart,
spade} cardSuit;
Pointer Notation
Read these symbols as follows:
• & - the address of
• * - value of the location pointed to by
/* Example 1 - read it aloud */
char x[10]= {“Hello World”};char * stringPtr;stringPtr = x;
stringPtr = &x[0]; // same effect as the above line
*(stringPtr+0) = ‘J’;
*(stringPtr+6) = ‘Z’; //becomes “Jello Zorld”
void *anything; //Not a pointer to nothing. A pointer to any type!
Pointers and Arrays are (almost) the Same
The following statements do exactly the same thing:
X[5] = 7;
*(x+5) = 7;
*(5+x) = 7;
5[x] = 7; //Yes, this is legal in C!
Pointer Notations with structs
• . - the member variable
• -> that points to
/* Example 2 – read it aloud to aid understanding */
struct PERSON{
char name[10];int age;char sex;};
struct PERSON people[10];struct PERSON *ptrPerson;
people[3].sex = ‘F’;ptrPerson = &people[3];ptrPerson -> age = 24;
(We’ll see this later with passing parameters to functions)
Declaring Arrays• char name[20];
• double matrix[10][12];
• int cube[5][5][5];
The dimensions of an array
are always constants
The values of subscripts range
from 0 to n-1
C just calculates an offset from
The base of the array – there is
no bounds checking!!!!
Initializing members of an Array
C99 allows us to specify particular elements
int nums[100]={1,2,3,4,5, [50]=42,[22]=0};
The last subscript changes more quickly
int matrix[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
Strings are implemented as Arrays
char name[]={“Hello World”};
• double quotes are used,
not single quotes
• strings are terminated by
a null character
• unicode (wchar_t - wide 4
byte characters) are not
well supported
Declaring Structs
A struct is a new data type made
up of other data types (including
other structs)
struct Person
{
char name[10];
unsigned int age;
double income
unsigned short nKids;
char * next;
};
struct Person joe,ellen, voters[20];
Initializing data in a declaration
float icecream=2.1f;
double mint=2.1;
char name[]={“Hello Central”};
The last subscript changes more quickly
int matrix[3][4]= { {1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
char names[][10] = {"Andrea", "Bob", "Singh"};
struct PERSON
{ char name[10];
int age;
char sex;
} ;
struct PERSON list[] = { {"Sally",21,'F'}, {"Rex",20,'M'}};
C99 extensions to initialization
C99 allows us to specify particular elements
int nums[100]={1,2,3,4,5, [50]=42,[22]=0};
C99 lets us define fields out of their regular order
struct PERSON peter={“Peter Jones”, .sex=‘M’, .age=22 };
Declaring Functions
A function returns a
value
double fn(double x)
{return x*2;}
A function without a
return type is assumed to
return a type of integer
fn2(double x,double y)
{
return (int) (x+y);
}
A subroutine is a function does not
directly return a value
void mySubroutine(double x)
{
fprintf(stdout,”Hello: %lf\n”, x);
return;
}
You should declare a function before its
first use. If you don’t the assumption is that it returns an int
int indexOf(const char *, char);
int indexOf(const char * string, char c)
{ //content of function goes here
}
Passing arguments in functions
• values are passed either by address(also called by reference) or by value
• primitive variables and constants are passed by value. fn1 gets a copyof the variable, not the original
• floats are always passed as doubles and converted back to floats.
• putting an & in front of a value means it will be passed by address
• arrays are always passed by address without adding the &
• structs are always passed (copied) by value (unless you add the &)
/* Only arguments passed by value result in a change to the original parameter */
void fn1(int arg1)
{
arg1++; //arg1 does not change outside
} //of fn1 because it’s a copy
void add1 (int * arg1)
{
(*arg1)++; // arg1 changes in calling
} // routine too.
num=21;
fn1(num); /* fn1 can’t change num */
add1(&value); /* fn2 does change num */
The Truth about Passing Arguments to Function
All arguments in C are passed by value. Its just that
some values happen to be addresses
char name[20];
double cross;
result=myFunction(name);
result2=myFunction(&cross)
main is a special functionmain is the name of the function that starts your
program. It has 3 arguments and it returns an 8 bit
integer value that represents the completion status code
of your program to the host operating system. A return
value of zero indicates program success. A non-zero
return value indicates some kind of failure, but you
decide on the meaning of the code.
The 3 arguments to main are:
• argc
the number of arguments in the command line.
• *argv[ ]
an array of strings. Each entry in the array points to a different command line argument
argv[0] refers to the name of the programargv[1] refers to the 1st command line argumentargv[2] refers to the 2
argv[argc] is null
• *envp[ ]
an array of strings. Each entry in the array points to a different environment variable. The last entry is null
int main(int argc, char * argv[],
char * envp[])
{
int i;
/* print out the command line arguments */
fprintf(stdout,”\nCommand Line args\n\n”);
for(i=0; i<argc; i++)fprintf(stdout,”argv[%d]: %s”, i, argv[i]);
/* print out the environment variables */
fprintf(stdout,”\n\nEnvironment Variables\n”);
for(i=0; envp[i]; i++)fprintf(stdout,”envp[%d]: %s, value: %s\n”,
i, envp[i], getenv(envp[i]));
return 0;
}
A function is just an address in
memory.
A function is a data type
that represents a block
of code
int myFunc()
{
return 20;
}
fprintf(stdout, “myFunc is located at address %x\n”, myFunc);
Functions are data types too
0x0AF00080: myFunc
Advanced: Storing functions in variables
int function1(double x) { return (int)(x*x); }
int function2(double y) { return (int)(-y); }
…
//Declaring the function pointer
int (*functionPtr)(double);
//Standard notation: portable
functionPtr=function1;
fprintf(stdout,”Eg1: %d\n”,(*functionPtr)(5.7));
//Non-standard notation: less portable
functionPtr=function2;
fprintf(stdout,”Eg2: %d\n”,functionPtr(5.7));
(*functionPtr)
functionPtr stores the address of
function1 or function2 as needed
Callback FunctionsThe syntax can be a little tricky but passing functions to functions is
fairly common to systems programming. Functions passed to functions are
called “callback functions”.
void mySort(void * values,
int (*compare)(void * a, void *b),
size_t size, int nItems)
{
…
if( (*compare)( values[i*size],values[j*size]) { //swap }
}
Declaration Modifiers
• auto
variable automatically released when the function or block of code is exited. The keyword is unnecessary as all variables are automatically considered auto
• static
variable is retained as long as the program is being run. Opposite of auto.
• extern
the variable was created elsewhere - outside the current function or outside the current file
• volatile
the variable can be modified by another program or a physical device. A warning to the compiler not to apply optimization techniques to the variable.
• register
A suggestion to the compiler that the variable is used frequently and should be stored in a CPU register rather than in conventional RAM memory. This is supposed to optimize the program. Compilers usually ignore this suggestion so it rarely works.
• const
Indicates that the value will never change after its initial declaration. Wnen used with a function parameter it indicates that the function will never change the value.
Operators in C
• Assignment: =
• Arithmetic : * / + - ++ -- %
• Logical: && || !
• Relational: > < == <>
• Bitwise: & | ^ << >> ~
• Pointer: -> * **
• Grouping: () [] ,
• Triadic: (cond) ? value1 : value2
Operators in C
• Operators can be binary or unary.
• ?: is a special triadic operator.
• Any binary operator can be combined with the assignmentoperator, ie:
a*=3;
is the same as
a=a*3;
• Assignment isn’t special – its an operator like any other
a=b+(c=7);
Expressions: Operator Precedence
comma: sequence operator,13
Arithmetic Assignment
Bitwise assignment
= += -= *= /= %=
&= |= ^= <<== >>=
12
logical and, logical or (short circuit operators&& ||11
bitwise or|10
bitwise xor^9
bitwise and&8
equal to, not equal to== !=7
less than, less than or equal to
greater than, greater than or equal 2
< <=> >=
6
bitwise left shift, bitwise right shift<< >> 5
plus, minus+ -4
times/divide/mod* / % 3
Unary pre/post increment/ decrementUnary plus/minusUnary logical negation/bitwise complementUnary cast (change type)DereferenceAddress of
size in bytes
++ --+ -! ~
(type)*&
sizeof
2
Parentheses (grouping)Brackets (array subscript)Member selection
Member selection via pointer
()[].
->
1
DescriptionOperatorPriority
Expressions: Operator Precedence
• When in doubt use brackets ()
• If you know the correct operator precedence, but aren’t so sure others will know it as well – use use brackets ()
• use brackets to make your meaning clear ( : - )
• did I mention you should use () ?
Expressions: Type Precedence
• float + double → double
• float + int → float
• short * long → long
• char + int → int
• address + int → address
When 2 operands are of
different types, the result is the
larger or more complicated type
When in doubt of the result, use the
cast operator
result = (float) (myDouble + myInt);
Expressions: Type Precedence
Remember:
x = 1/4; /* x 0 */
x = 1.0/7; /* x .25*/
C does very little type
checking!!
Flow of Control: IF stmt
if(condition) stmt;
else stmt;
if(x>10) puts(“Too Big”);
else puts(“Value OK”);
Flow of Control: if stmt
if(x>10 && x<20)
{
/* block of code */
}
else
{
/* another block of
code */
}
Secret Slide
The triadic operator is a
shorter form of if/then/else
examples:
if(x>10) y=2; else y=0;
y= (x>10) ? 2 : 0
if(x>10) fprintf(stdout,”X is big\n”);
else fprintf(stdout,”X is small\n”);
fprintf(stdout, (x>10) ? “X is big\n” : “x is small”);
if(a>b) return a; else return b;
return (a>b) ? a : b;
Flow of Control: switch/case
switch(ordinalValue)
{
case ‘A’:
case ‘a’:puts(“A chosen”);
break;
case 2:
puts(“# 2 choice”);
break;
default:
puts(“None of the above”);
}
Flow of Control: while
while(condition)
{
int localValue;
doStuff();
}
Variables can be
declared at the start
of any block of code
{ }
Forever loop: while
while(true)
{
int localValue;
doStuff();
}
Variables can be
declared at the start
of any block of code
{ }
Flow of Control: do while
do
{
doStuff();
} while(condition);
This kind of loop is
always executed at
least once. The test
is at the end of the
loop
Flow of Control: for loops
for(initialization;
condition; increment)
{
doStuff();
...
}
doStuff
increment
Test
initialization
The break and continue statements
for(i=0;i<10;i+=2)
{
for(init; cond; incr)
{
.... some code ...
if(condition1) break;
...
if(condition2) continue;
}
}
break: exits the inner loop
continue: jump to the end of the inner loop and loops around again
Flow of Control: For Loops (cont’d)
for(int i=0;i<10;i++) {}
for(i=10;i; i--) {}
for(;i;i--) {}
for(;condition;) {}
for(;;) {}
• Unlike java you can’t declare a variable inside the initialization section – unless ....
gcc –std=gnu99 myprog.c
• Ordinal values can be used as conditions
• Each field of the for stmt is optional
• leaving out the condition makes the for stmt loop for ever
for loops: Common Patterns
• iterating through a string
for(i=0;string[i];i++)fputc(string[i], outFile);
• iterating through a linked list
{
struct LLIST * temp;for(temp=head; temp; temp=temp->next)
fprintf(stdout,”Value: %s\n”, temp->value
}
Flow of control: assert(logical expression)
If the compiler directive -DNDEBUG is used this has no effect.
If the expression is true nothing happens.
If the expression is false then the expression is output along with the file name and the line number and the program terminates. If an output dump is permited(ulimit -c nonzerovalue) then a program dump is created.
assert(fp=fopen(“datafile”,”r”)); //program fails is file not available
Flow of Control: Labels and the dreaded Goto
• You probably were not taught about the goto statement
• If you were, you were told that
it was bad
• use break, continue, exit(n)
instead
• Use it only in emergencies ....
Allocating Memory
#include <malloc.h> //functions that allocate memory on the heap
#include <alloca.h> //Linux only – allocates memory on the stack
char * newString=malloc(100); //100 bytes allocated
struct SOMETHING date=calloc(100,sizeof struct SOMETHING);
char * newString=malloc(100); //100 bytes allocated
struct DATA data=calloc(100,sizeof(struct DATA)); //zeroes memory
data=realloc(data, n*sizeof(struct DATA)); //resizes allocated memory
int status=posix_memalign(&data,sizeof(double), n*(sizeof(struct data));
newString=alloca(nBytes); //allocates memory on the stack, not recommended
free(data) //Releases storage back to the heap
Macros
#define max(a,b) (a>b?a: b)
#define display(a) (fprintf(stdout,
“The value is: %d \n”, a);
display(max(x[i+j],x[i+k));
A macro can be used to define an expression
#define PI 3.141529
#define TRUE 1
#define FALSE 0
#define MAX_FILES 26
A macro can be used to define a value
Preprocessor Commands: Conditional Compilation
#include <stdio.h>
#include “myInc.h”
#define DEBUG
#ifdef DEBUG
fprintf(stdout,”Value of x is: %d\n”,x);
#else
/* Do nothing */
#endif
insert text from a file
I/O
C has no I/O commands. All I/O is performed using library functions
Character & String I/O• fputc(int c,FILE *stream);
• fputs(const char *s, FILE * stream)
• int putc(int c, FILE * stream);
• int putchar(int c);
• int puts(const char *s);
• int fgetc(FILE *stream);
• char fgets(char *s, int size,FILE *stream);
• int getc(FILE *stream);
• int getchar(void);
• char *gets(char *s);
• int ungetc(intc FILE *stream);
A positive return value is the value read or written.
A negative return value is interpretted as an error code.
Formatted IO: Don’t Use
printf(“fmt string”, varList....)
scanf(“fmt string”, &varList...)
Don’t use these any more
Formatted I/O
• int scanf(const char * format, &arg1, &arg2 ...);
• int fscanf(FILE *stream, const char *format,&arg1, ...);
• int sscanf(const char * str, const char *format, &arg1, ...);
• int printf(const char * format, arg1, arg2 ...);
• int fprintf(FILE *stream, const char *format,arg1, arg2 ...);
• int sprintf(const char * str, const char *format, arg1, ...);
A positive or zero result is the number of fields input or output.
A negative return value is interpretted as an error code.
Format Codes
• %5d - decimal
• %x - hexadecimal
• %f - float
• %7.2lf – long float (double)
• %c – character
• %10s – string
• %g - general float
• %p - pointer
• for more consult your C text, online man pages or any good C references.
There are 3 standard i/o streams for every Unix process• stdin
• stdout
• stderr
Files can be open for input and output#include <stdio.h>
int main()
{
FILE * infile;
File * outfile;
infile=fopen(“file1.txt”,”rt”);
outfile=fopen(“output.txt”,”wt”);
/* Checking that we could open the file – file may not exist*/
if(!infile) { perror(“Failure to open file1 for input”);
exit(1); /* return with an error code */
}
/* Checking that we could open the file – we may be in a directory
where we don’t have permission to open the file */
if(!outfile){ perror(“Failure to open output.txt”);
exit(2); /* return with an error code */
}
}
Formatted Input
/* stdin can be treated like a file */
fscanf(stdin,”%d%lf%20s”,&i,&mint,name);
fscanf(infile,”%d%lf%20s”,&i,&mint,name);
Use fscanf instead of scanf – specifying where output
goes all the time makes it less confusing.
Formatted Output
/* stdout can be treated like a file */
fprintf(stdout,”%d: 20s\n”, count, name);
fprintf(stderr,”%d: 20s\n”, count, name);
fprintf(outfile,”%d: %20s”, count, name);
Use fprintf instead of printf – it makes things
less confusing.
Block I/O (This is probably new to you)
typedef unsigned long size_t; /*see def’n in /usr/include/stdio.h */
• size_t fread(void *memoryBlock,size_t nBytes, size_t nItems, FILE *file);
• size_t read(int fileDescriptor,void * memoryBlock, unsigned int nBytes);
• size_t fwrite(void * memoryBlock, size_t nBytes, size_t
nItems,FILE *file);
• size_t write(int fileDescriptor,void *memoryBlock, unsigned int nBytes);
For read and write the value returned is the number of bytes
successfully transfered. For fread and fwrite the value
returned is the number of items (records) read in.
What’s the difference between a file
pointer (FILE *) and a file descriptor
Not much – it’s two ways to refer to a file
• File descriptors are just integers
• A file descriptor of zero is stdin
• A file descriptor of 1 is stdout
• A file descriptor of 2 is stderr
• A file descriptor of 3 is the 1st file you open
• A file descriptor of 4 is the 2nd file that you open, etc...
We’ll see more on this topic later in CENG251
Secret Slide
Your C program maintains
array of FILEs. stdin points to
the 0th entry, stdout points to the
1st...
#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])
You can find these definitions in
/usr/include/stdio.h
Copying files using a file descriptor/* Fast Copying of a File */
#include <fcntl.h>
#define BLOCKSIZE 10000
int main(int argc, char * argv[])
{
int inFileDes, outFileDes, nBytes;
char memory[BLOCKSIZE];
int mode=0660; /* octal permissions: owner, group rw */
inFileDes=open(argv[1], O_RDONLY);
outFileDes=open(argv[2], O_WRONLY | O_CREAT, mode);
fprintf(stdout,”inFileDes: %d outFileDes: %d\n”, inFileDes, outFileDes);
if(inFileDes>=0 && outFileDes>=0) /* copy file if both file descriptors are open */
do
{
nBytes=read(inFileDes, memory,BLOCKSIZE);
write(outFileDes, memory, nBytes);
}
while(nBytes>0);
return 0;
}
Same Program, File pointers/* Fast Copying of a File */
#include <stdio.h>
#define BLOCKSIZE 10000
int main(int argc, char * argv[])
{
FILE *infile, *outfile;
int nBytes;
char memory[BLOCKSIZE];
infile=fopen(argv[1], ”rb”);
outfile=fopen(argv[2], “wb”);
if(infile && outfile) /* copy file if both file pointers are not null */
do
{
nBytes=fread(memory,BLOCKSIZE,1,infile);
fwrite(memory, nBytes,1, outfile);
}
while(nBytes>0);
return 0;
}
Random Access: Repositioning in a file
position=fseek(fp,distance,whence)
position=lseek(fd,distance,whence)
distance is a long integerwhence is one of 3 predefined constants: SEEK_SET (count from beginning of file)
SEEK_CUR (count from current position)SEEK_END (count from end of file)
fseek(fp,80L,SEEK_SET) //Next I/O operation will start 80 bytes from start of filelseek(fd,-1000L,SEEK_END) //Next I/O begins 1000 bytes before the end of filefseek(fp,100L,SEEK_CUR) //Advance 100 bytes from last position in the file
oldPosition=lseek(fd,0L,SEEK_CUR) //Record current position in file
fileSize=lseek(fd,0L,SEEK_END)
pread and pwrite (positioned I/O)
These functions which work with file descriptors only allow one to read or write from a specified location in a file without changing the file offset for the next operation.
bytesRead=pread(fd,buffer,nBytes,offset);
bytesWritten=pwrite(fd,buffer,nBytes,offset);
Helper files you probably know of
• ctype.h (isalpha, isdigit, tolower, toupper ...)
• malloc.h (malloc, calloc, free)
• math.h (abs, min, max, sin, cos, exp ...)
• stdio.h (fprintf, fscanf, fopen, fclose)
• stdlib.h (type conversions, random #s)
• string.h (strlen, strcmp, strcat, strdup…)
• unistd.h (sleep, read, write, close)
Using gcc
gcc myProg.c myFunctions.c myLib.o \
-o myProg
GCC Options
• -c compile but don’t link. generate –o files
• -Dvariable=value define a macro variable for the compiler
• -E generate preprocessed listing only
• -S generate assembler listing
• -g3 prepare for debugger. Level 3 makes macros available to gdb
• -Idir add a different include directory to search
• -lxxx link in library libxxx.a
• -Ldir add a different library directory to search
• -p prepare code for use with profile prof
• -v verbose. show stages of compilation
• -wFormat - check format codes match argument types in fprintf/fscanf
• -std=c99 use a particular C specification (gnu99, gnu89…)
and many more....
man sections
The man section for a function appears at the top left its man page. Man
pages are stored under the /user/share/man directory. Review the list of
files under each subdirectory to get an idea of how topics are grouped
• (1) Unix commands
• (2) C System calls
• (3) C Library calls (not requiring system privileges)
• (4) Devices and special files (ie: man null; man 4 tty; man random; man pts);
• (5) File formats (ie: man passwd, man proc, man elf)
• (6) Games (unfortunately many organizations do not install these)
• (7) Miscellaneous (man ascii; man 7 time, man 7 signal; man Unicode)
• (8) System administration tools (requires adm privilege to use)
• (9) Kernel developer functions (requires root privilege to use)
Man man will display a list of sections
apropos topic displays a list of related topics along with their section – very useful!
Setting the MANPATH variable defines additional man directories to be searched
The Development Process
debug
profile
run
compile
syntax
check
Document
edit
test
analyze
Unix Tools
• vim
• indent
• lint
• gcc
• ctags
• gdb, ddd, kdevelop
• prof
On the road to becoming an expert...
• learn the language
• learn the libraries
• learn the software patterns that are commonly used
• what you don’t know, learn how to learn: man pages, books, sample code
Message from the CENG Faculty
1. Document
2. Code
3. Design Test Cases
4. Debug
5. Write it up
6. Ask for advice and help when needed
7. Hand it in with a summary/status report including a description of outstanding problems.
8. A nearly working program with a good diagnostic is more valuable than a working program without valid testing or diagnostic.
End of C Review
List of Changes to Slides in 2021
• memory allocation functions added
• reorganized and added to helper file list
• non-review items fseek, lseek
• non-review items pread and pwrite
• non-review item assert
• exit and return statements
• Added a list of man sections
• compiler directives -D, -g3, -wformat
• Unicode constants and display added