C's 37 reserved words:
Identifiers in C:
auto enum restrict unsigned
break extern return void
case float short volatile
char for signed while
const goto sizeof _Bool
continue if static _Complex
default inline struct _Imaginary
do int switch
double long typedef
else register union
Note:
int myAge;
is different from
int MyAge;
int abcdefghijklmnopqrstuvwxyz12345;
and
int abcdefghijklmnopqrstuvwxyz12346;
but compilers are not guaranteed to distinguish between
int abcdefghijklmnopqrstuvwxyz123456;
and
int abcdefghijklmnopqrstuvwxyz123457;
num_courses_taken = compute_num_courses_taken (student_ID);
numCoursesTaken = computeNumCoursesTaken (studentID);
for (i=0; i < 10; i++) {
A[i] = B[i] * C[i];
}
In-Class Exercise 2.1:
What kind of a compiler error do you get if you try to use
a reserved word as an identifier?
There are two types of comments in ANSI C99:
to the end of the line.
#include <stdio.h>
/* This is a block comment. Everything between the begin-comment symbol
and the end-comment symbol is ignored by the compiler. It is conventional
to use a separate line for the end-comment symbol.
*/
// This is an in-line comment.
int main (/* A strange place for a block comment */)
{
printf ("Hello World!\n"); // Another inline comment.
}
Note:
gcc -o helloworld -std=c99 helloworld.c
gcc -o helloworld helloworld.c
also works, but will not check for ANSI C99 compatibility.
About C's data types:
Integer types (ANSI C99 additions shown in bold):
| Type | Typical storage size | Range | Print specifier |
| int | 2 or 4 bytes | -32,768 to 32,767 (2 bytes)
-2,147,483,648 to 2,147,483,647 (4 bytes) |
%d |
| unsigned int | 2 or 4 bytes | 0 to
65,535 (2 bytes) 0 to 4,294,967,295 (4 bytes) |
%u |
| short | 2 bytes | -32,768 to 32,767 | %d |
| unsigned short | 2 bytes | 0 to 65,535 | %u |
| long | 4 bytes | -2,147,483,648 to 2,147,483,647 | %ld |
| unsigned long | 4 bytes | 0 to 4,294,967,295 | %lu |
| long long | 8 bytes | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | %lld |
| unsigned long long | 8 bytes | 0 to 18,446,744,073,709,551,615 | %llu |
Let's look at an example:
(source file)
#include <stdio.h>
int main ()
{
int numDaysInYear = 365;
long int numStarsInUniverse = 2000000000L;
unsigned long long int largestIntegerInC = 18446744073709551615LL;
printf ("numDaysInYear = %d\n", numDaysInYear);
printf ("numStarsInUniverse = %ld\n", numStarsInUniverse);
printf ("largestIntegerInC = %llu\n", largestIntegerInC);
}
Note:
int main ()
{
int numDaysInYear = 365;
printf ("numDaysInYear = %d\n", numDaysInYear);
if (numDaysInYear < 366) {
long int numStarsInUniverse = 2000000000L;
printf ("Thank your lucky %ld stars\n", numStarsInUniverse);
}
else {
unsigned long long int numStarsInUniverse = 1844674407370955161LL;
printf ("Thank your lucky %llu stars\n", numStarsInUniverse);
}
}
int main ()
{
int i;
for (i=0; i<10; i++) { // Allowed.
printf ("i=%d\n", i);
}
for (int j=0; j<10; j++) { // Not allowed. j needs to be declared at the top.
printf ("j=%d\n", j);
}
}
int main ()
{
int numDaysInYear = 365; // Allowed.
printf ("numDaysInYear = %d\n", numDaysInYear);
long int numStarsInUniverse = 2000000000L; // Not allowed.
printf ("Number of stars = %ld stars\n", numStarsInUniverse);
}
#include <stdio.h>
// Declaration and assignment.
int numDaysInYear = 365;
int main ()
{
// Declaration.
long int numStarsInUniverse;
printf ("numDaysInYear = %d\n", numDaysInYear);
// Assignment.
numStarsInUniverse = 2000000000L;
printf ("numStarsInUniverse = %ld\n", numStarsInUniverse);
}
For variety, we have declared numDaysInYear as a global
variable.
int main ()
{
int numDaysInYear = 365;
long numStarsInUniverse = 2000000000L;
unsigned long long largestIntegerInC = 18446744073709551615LL;
printf ("numDaysInYear = %d\n", numDaysInYear);
printf ("numStarsInUniverse = %ld\n", numStarsInUniverse);
printf ("largestIntegerInC = %llu\n", largestIntegerInC);
}
Screen output:
printf (format-string [zero or more variables])
int main ()
{
int numDaysInYear = 365;
long int numStarsInUniverse = 2000000000L;
unsigned long long int largestIntegerInC = 18446744073709551615LL;
printf ("numDaysInYear = %d numStarsInUniverse = %ld largestIntegerInC = %llu\n",
numDaysInYear, numStarsInUniverse, largestIntegerInC);
}
int main ()
{
int numDaysInYear = 365;
long int numStarsInUniverse = 2000000000L;
unsigned long long int largestIntegerInC = 18446744073709551615LL;
// Up to 5 digits:
printf ("numDaysInYear = %5d\n", numDaysInYear);
// Up to 10 digits:
printf ("numStarsInUniverse = %10ld\n", numStarsInUniverse);
// Up to 20 digits:
printf ("largestIntegerInC = %20llu\n", largestIntegerInC);
}
#include <stdio.h>
int main ()
{
int numDaysInYear = 365;
long int numStarsInUniverse = 2000000000L;
unsigned long long int largestIntegerInC = 18446744073709551615LL;
printf ("numDaysInYear = %d numStarsInUniverse = %ld largestIntegerInC = %llu\n",
numDaysInYear, numStarsInUniverse, largestIntegerInC);
}
What happens when you mistakenly use %d for all the integers
above?
Floating-point types:
| Type | Typical storage size | Range | Print specifier | Approximate precision |
| float | 4 bytes | 1.2 x 10-38 to 3.4 x 1038 | %f | 6 decimal places |
| double | 8 bytes | 2.3 x 10-308 to 1.7 x 10308 | %lf | 15 decimal places |
| long double | 10 bytes | 3.4 x 10-4932 to 1.1 x 104932 | %llf | 19 decimal places |
Consider this example:
(source file)
int main ()
{
// Constant with "F" appended:
float PI = 3.141F;
// Constant in exponent format:
double doublePI = 314.159265E-2;
// Long double constant:
long double ldoublePI = 3.14159265358979L;
// Output in exponent format:
printf ("float PI = %7.5e\n", PI);
// Output in decimal format with field width and number of significant digits:
printf ("double PI = %16.10lf\n", doublePI);
// Long double's need to be printed as double's
printf ("long double PI = %15.12lf\n", (double) ldoublePI);
}
Note:
float PI = 3.141F;
double doublePI = 314.159265E-2;
Character types:
| Type | Typical storage size | Range | Print specifier |
| char | 1 byte | -128 to 127 | %c |
| unsigned char | 1 byte | 0 to 255 | none |
| signed char | 1 byte | -128 to 127 | none |
Consider this example:
(source file)
int main ()
{
char letter = 'a';
unsigned char letter2 = 'b';
signed char letter3 = 'c';
printf ("letter = %c\n", letter);
printf ("letter2 = %c\n", letter2);
printf ("letter3 = %c\n", letter3);
}
Note:
Consider this example:
(source file)
int main ()
{
int i = 5;
long j = 6;
double d = 3.141;
j = i; // Works fine. Implicit cast from int to long.
d = i; // Works fine. Implicit cast from int to double
i = j; // May not compile.
i = d; // May not compile.
d = 3.141;
i = (int) j; // Compiles. Explicit cast from long to int.
i = (int) d; // Compiles. Explicit cast from double to int.
// Cast's can be used in any expression:
printf ("The int part of d=%lf is %d\n", d, (int) d);
}
Note:
i = (int) d;
short -> int -> unsigned int
-> long -> unsigned long -> long long
-> float -> double -> long double
What is a pointer?
Consider an example with char pointers:
(source file)
Examining pointers in a debugger:
Let's look at an example:
(source file)
// Declare some global integers:
int i = 5;
int j = 6;
int sum;
int main ()
{
// Int pointer declarations:
int *intPtr;
int *intPtr2;
int *intPtr3;
// First, let's print the address of variable i:
printf ("Variable i is located at address %lu\n", &i);
// Now extract the address of variable i into the pointer:
intPtr = & i;
// Print.
printf ("The int at memory location %lu is %d\n", intPtr, *intPtr);
// Let's do some arithmetic.
intPtr2 = & j;
sum = (*intPtr) + (*intPtr2);
printf ("The sum of %d and %d is %d\n", *intPtr, *intPtr2, sum);
// Now for something stranger:
printf ("The integer at location %lu is %d\n", (intPtr+1), *(intPtr+1));
printf ("The integer j=%d is at location %lu\n", j, &j);
// Let's see what the default initial value of intPtr3 is:
if (intPtr3 == NULL) {
printf ("intPtr3 has been initialized to NULL\n");
}
else {
printf ("intPtr3 has been initialized to %lu\n", intPtr3);
}
}
Note:
// Now extract the address of variable i into the pointer:
intPtr = & i;
sum = (*intPtr) + (*intPtr2);
printf ("The integer at location %lu is %d\n", (intPtr+1), *(intPtr+1));
In this case, we examine the next address beyond i, which
happens to be where j is stored.
int main ()
{
int i = 5;
char *charPtr; // Pointer declaration
int j;
// Make the char pointer point to the start of the integer:
charPtr = (char*) (& i);
// Extract the byte, store it in the integer j and print j.
j = (int) *charPtr;
printf ("First byte: %d\n", j);
// Get the next byte and print:
j = (int) *(charPtr+1);
printf ("Second byte: %d\n", j);
// Get the third byte and print:
j = (int) *(charPtr+2);
printf ("Third byte: %d\n", j);
// Get the fourth byte and print:
j = (int) *(charPtr+3);
printf ("Fourth byte: %d\n", j);
}
Note:
char *charPtr; // Pointer declaration
j = *charPtr; // Retrieve whatever charPtr points to.
gcc -g -o pointer pointer.c
and run inside the debugger:
gdb pointer
(gdb) l
7
8 int main ()
9 {
10 // Int pointer declarations:
11 int *intPtr;
12 int *intPtr2;
13 int *intPtr3;
14
15 // First, let's print the address of variable i:
16 printf ("Variable i is located at address %lu\n", &i);
(gdb) break 16
Breakpoint 1 at 0x10720: file pointer.c, line 16.
(gdb) run
Starting program: pointer
Breakpoint 1, main () at pointer.c:16
16 printf ("Variable i is located at address %lu\n", &i);
(gdb) p intPtr
$1 = (int *) 0x0
(gdb) next
Variable i is located at address 134000
19 intPtr = & i;
(gdb) next
22 printf ("The int at memory location %lu is %d\n", intPtr, *intPtr);
(gdb) p intPtr
$2 = (int *) 0x20b70
(gdb) p *intPtr
$3 = 5
(gdb)
The l (list) command lists the program.
The break command creates a breakpoint so you can
stop midway through execution.
The p (print) command prints variables.
The next command executes the next statement.
In-Class Exercise 2.4:
Write a program to use a pointer to a pointer to an int.
Fill in the needed assignments below to make the program
print "5".
int main ()
{
int i = 0;
int *p = NULL;
int **p2 = NULL;
// Fill in the assignments here to make the program work:
// Should print "5";
printf ("i = %d\n", **p2);
}
An example with arithmetic operators
(source file)
An example with bitwise operators
(source file)
An example with boolean operators
(source file)
Boolean types:
int main ()
{
double x = 6, y = 5;
int i = 8, j = 5;
// Standard: plus, minus, multiple, divide
printf ("x+y=%lf\n", x+y); // Prints 11.0
printf ("x-y=%lf\n", x-y); // Prints 1.0
printf ("x*y=%lf\n", x*y); // Prints 30.0
printf ("x/y=%lf\n", x/y); // Prints 1.2
// Integer divide and remainder:
printf ("i/j=%d\n", i/j); // Prints 1
printf ("i mod j=%d\n", i%j); // Prints 3
// Post and pre-increment:
printf ("i++ = %d\n", i++); // Prints 8
printf ("++j = %d\n", ++j); // Prints 6
// Assignment shortcut example.
i += j;
printf ("i=%d\n", i); // Prints 15
}
int main ()
{
int a = 9;
int b = 4;
printf ("a&b = %d\n", a&b); // Bitwise AND: Prints 0
printf ("a|b = %d\n", a|b); // Bitwise OR: Prints 13
printf ("a^b = %d\n", a^b); // Bitwise EOR: Prints 13
printf ("~a = %d\n", ~a); // Complement: Prints -10
printf ("b << 1 = %d\n", (b << 1)); // Left shift by 1: Prints 8
printf ("b >> 2 = %d\n", (b >> 2)); // Right shift by 2: Prints 1
}
int main ()
{
double x = 5, y = 6, z = 6;
int result;
printf ("x < y = %d\n", (x < y)); // Prints 1 (true)
printf ("x <= y = %d\n", (x <= y)); // Prints 1
printf ("y > z = %d\n", (y > z)); // Prints 0 (false)
printf ("y >= z = %d\n", (y >= z)); // Prints 1
printf ("x == y = %d\n", (x == y)); // Prints 0
printf ("y == z = %d\n", (y == z)); // Prints 1
printf ("x != y = %d\n", (x != y)); // Prints 1
result = (x < y);
printf ("result=%d\n", result); // Prints 1
if (result == 1) { // Equality comparison.
printf ("x < y\n");
}
else {
printf ("x >= y\n");
}
// Prints "x < y"
}
Note:
int main ()
{
double x = 5, y = 6;
int result = (x < y);
if (result == 1) {
printf ("x < y\n");
}
else {
printf ("x >= y\n");
}
}
#define boolean int
#define true 1
#define false 0
int main ()
{
double x = 5, y = 6;
boolean result = (x < y);
if (result == true) {
printf ("x < y\n");
}
else {
printf ("x >= y\n");
}
}
#include <stdio.h>
#include <stdbool.h>
int main ()
{
double x = 5, y = 6;
bool result = (x < y);
if (result == true) {
printf ("x < y\n");
}
else {
printf ("x >= y\n");
}
}
#include <stdio.h>
#include <stdbool.h>
int main ()
{
double x = 5, y = 6;
_Bool result = (x < y);
if (result == true) {
printf ("x < y\n");
}
else {
printf ("x >= y\n");
}
}
Strings in C:
Example:
(source file)
int main ()
{
char *hStr = "hello"; // String initialization.
char *wStr = "world";
char ch;
// Note the use of "%s"
printf ("%s %s\n", hStr, wStr);
printf ("3rd char in wStr is %c\n", *(wStr+2)); // Prints 'r'
// Let's get the 6-th char: should be '\0'
ch = *(wStr+5);
if (ch == '\0') {
printf ("char is string terminator\n");
}
else {
printf ("char is not string terminator\n");
}
}
Note:
There are two ways of defining constants in C:
#define PI 3.14159
int main ()
{
double radius = 5.0;
printf ("Area of Circle with radius %lf is %lf\n", radius, PI*radius*radius);
}
const double PI = 3.14159;
int main ()
{
double radius = 5.0;
printf ("Area of Circle with radius %lf is %lf\n", radius, PI*radius*radius);
}
The latter approach is more general, and can be applied to function
parameters as well:
const double PI = 3.14159;
// "const" declaration makes it illegal for a function to
// change the input parameter.
double computeArea (const double radius)
{
return PI * radius * radius;
}
int main ()
{
double radius = 5.0;
printf ("Area of Circle with radius %lf is %lf\n", radius, computeArea(radius) );
}
An overview of C's control flow statements via examples:
(source file)
int main ()
{
int i = 1;
// if-statement:
if (i == 0) {
printf ("i is zero\n");
}
// if-statement with compound expression:
if ( (i >= -1) && (i <= 1) ) {
printf ("-1 <= i <= 1\n");
}
// if-else combination
if (i == 1) {
printf ("one\n");
}
else if (i == 2) {
printf ("two\n");
}
else {
printf ("larger than two\n");
}
// Variation of if-else above:
if (i == 1) {
printf ("one\n");
}
else {
if (i == 2) {
printf ("two\n");
}
else {
printf ("larger than two\n");
}
}
// Equivalent switch statement:
switch (i) {
case 1: {
printf ("one\n");
break;
}
case 2: {
printf ("two\n");
break;
}
default: {
printf ("larger than two\n");
}
}
// for-loop example:
printf ("Numbers 0 through 9: \n");
for (i=0; i < 10; i++) {
printf (" %d\n", i);
}
// while-loop equivalent:
printf ("Numbers 0 through 9: \n");
i = 0;
while (i < 10) {
printf (" %d\n", i);
i++;
}
// do-while equivalent:
printf ("Numbers 0 through 9: \n");
i = 0;
do {
printf (" %d\n", i);
i++;
} while (i < 10);
// Example of using "break"
printf ("Numbers 0 through 9: \n");
i = 0;
while (1) {
if (i == 10) {
break;
}
printf (" %d\n", i);
i++;
}
printf ("Odd numbers less than 10:\n");
i = 0;
while (1) {
// If we've reached the limit, break out of the loop.
if (i == 10) {
break;
}
// If it's an even number, skip to next iteration of loop.
if (i % 2 == 0) {
i++;
continue;
}
// Print odd number.
printf (" %d\n", i);
i++;
}
}
Note:
for (int i=0; i < 10; i++) {
// ...
}
Versions prior to C99 do not. For compatibility with many textbooks,
we will declare variables at the top.
There are two ways of creating an array in C:
Let's look at an example:
(source file)
int main ()
{
int A[10]; // Statically-sized unidimensional array.
double B[20][20]; // 2D array.
int *A2 = NULL; // Declaration of 1D array variable.
double **B2 = NULL; // Declaration of 2D array variable.
int i, j; // For-loop variables.
int sum; // For use in examples.
double dSum;
// Static example. The space is already allocated, so the
// array can be used immediately.
for (i=0; i < 10; i++) {
A[i] = i * 100; // Fill the array with some numbers.
}
sum = 0;
for (i=0; i < 10; i++) {
sum += A[i]; // Compute the sum of array's numbers.
}
printf ("sum = %d\n", sum); // Print sum.
// Dynamic version of example using "malloc" to allocate space.
// Note the use of the "sizeof" keyword.
A2 = (int*) malloc (sizeof(int) * 10);
for (i=0; i < 10; i++) {
A2[i] = i * 100; // Fill the array with some numbers.
}
sum = 0;
for (i=0; i < 10; i++) {
sum += A2[i]; // Compute the sum of array's numbers.
}
printf ("sum = %d\n", sum); // Print sum.
// 2D example with static array.
for (i=0; i < 20; i++) {
for (j=0; j < 20; j++) {
B[i][j] = i*j; // Fill the array with some numbers.
}
}
dSum = 0;
for (i=0; i < 20; i++) {
for (j=0; j < 20; j++) {
dSum += B[i][j]; // Compute the sum of array's numbers.
}
}
printf ("dSum = %lf\n", dSum); // Print sum.
// Dynamic version of 2D example. Note the two-step allocation.
// Allocate space for 20 pointers-to-double
B2 = (double **) malloc (sizeof(double*) * 20);
for (i=0; i < 20; i++) {
// For each such pointer, allocate a size-20 double array.
B2[i] = (double*) malloc (sizeof(double) * 20);
}
for (i=0; i < 20; i++) {
for (j=0; j < 20; j++) {
B2[i][j] = i*j; // Fill the array with some numbers.
}
}
dSum = 0;
for (i=0; i < 20; i++) {
for (j=0; j < 20; j++) {
dSum += B2[i][j]; // Compute the sum of array's numbers.
}
}
printf ("dSum = %lf\n", dSum); // Print sum.
// Must free allocated memory when not needed anymore:
free (A2);
free (B2);
}
Note:
by
using the sizeof operator.
A2 = (int*) malloc (sizeof(int) * 10);
Note:
int *A2 = NULL; // Declaration of 1D array variable.
spaceNeededInBytes = sizeof(int) * 10;
A2 = (int*) malloc (spaceNeededInBytes);
A2 = (int*) malloc (sizeof(int) * 10);
B2 = (double **) malloc (sizeof(double*) * 20);
for (i=0; i < 20; i++) {
B2[i] = (double*) malloc (sizeof(double) * 20);
}
Note:
B2 = (double **) malloc (sizeof(double*) * 20);
for (i=0; i < 20; i++) {
B2[i] = (double*) malloc (sizeof(double) * 20);
}
1 1
1 2 1
1 3 3 1
1 4 6 4 1