CSCI 2021: C Basics
Last Updated:
Fri Sep 24 08:54:03 AM CDT 2021
1
Logisitcs Reading
▶ C references
▶ Bryant/O’Hallaron Ch 2.1-3
on Binary Reps
Goals
▶ Complete C overview ▶ Binary Reps (next)
Assignments
▶ P1 Up / Ongoing
▶ Lab03 on File Input ▶ HW03 on Binary Ints
Date
Wed 09/22
Fri 09/24 Mon 09/24 Wed 09/29 Fri 10/01
NOTE: will post P1 overview video later today
Event
Complete C Intro Lab03
Binary Ints/Chars Binary Ints/Chars Lec/Lab Review Exam 1
Project 1 Due
Questions?
2
Announcement: Google Student Developers Club Event
https://piazza.com/class/ksuvqq1tdpo2jx?cid=45
▶ Talk sponsored by GSDC ▶ In-person or via Zoom
3
Every Programming Language
Look for the following as it should almost always be there ⊠ Comments
⊟ Statements/Expressions
⊟ Variable Types
⊟ Assignment
⊟ Basic Input/Output
□ Function Declarations
□ Conditionals (if-else)
□ Iteration (loops)
□ Aggregate data (arrays, structs, objects, etc) □ Library System
4
Exercise: Traditional C Data Types These are the traditional data types in C
Bytes* Name INTEGRAL
1 char
2 short
4 int 8 long
FLOATING 4 float
8 double POINTER
4/8 pointer array
Range
-128 to 127
-32,768 to 32,767
-2,147,483,648 to 2,147,483,647 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
±3.40282347E+38F (6-7 significant decimal digits) ±1.79769313486231570E+308 (15 significant decimal digits)
Pointer to another memory location, 32 or 64bit double *d or int **ip or char *s or void *p (!?) Pointer to a fixed location
double [] or int [][] or char []
*Number of bytes for each type is NOT standard but sizes shown are common. Portable code should NOT assume any particular size which is a huge pain in the @$$.
Inspect types closely and discuss the following:
1. Ranges of integral types? 3. void what now?
2. Missing types you expected? 4. How do you say char?
5
Answers: Traditional C Data Types Ranges of signed integral types
Asymmetric: slightly more negative than positive
char is -128 to 127
Due to use of Two’s Complement representation, many details and alternatives later in the course.
Missing: Boolean
Every piece of data in C is either truthy or falsey:
int x; scanf(“%d”, &x);
if(x){ printf(“Truthy”); } // very common
else { printf(“Falsey”); }
Typically 0 is the only thing that is falsey Missing: String
▶ char holds a single character like ‘A’ or ‘5’
▶ No String type: arrays of char like char str[] or char *s ▶ char pronounced CAR / CARE like “character” (debatable)
6
Recall: Pointers, Addresses, Derferences
type *ptr Declares a pointer variable
*ptr
Dereferences pointer to get/set value pointed at
1 int *iptr;
2 intx=7;
3 iptr = &x;
4 int y = *iptr; 5 *iptr = 9;
6
7 double z = 1.23;
8 double *dptr = &z;
9 *dptr = 4.56;
10
11 printf(“x: %d z: %f\n”,
12 *iptr, *dptr);
// Declare a pointer
// Declare/set an int
// Set pointer
// Deref-ptr, gets x
// Deref-set ptr, changes x
// Declare/set double
// Declare/set double ptr
// Deref-set ptr, changes z
// print via derefs
Declaring pointer variables to specific types is the normal and safest way to write C code but can be circumvented
7
Exercise: Void Pointers void *ptr; // void pointer
▶ Declares a pointer to something/anything
▶ Useful to store an arbitrary memory address
▶ Removes compiler’s ability to Type Check so introduces risks managed by the programmer
Example: void_pointer.c
▶ Predict output
▶ What looks screwy
▶ Anything look wrong?
File void_pointer.c:
1 #include
3 inta=5;
4 double x = 1.2345; 5 void *ptr;
6
7 ptr = &a;
8 int b = *((int *) ptr);
9 printf(“%d\n”,b);
10
11 ptr = &x;
12 double y = *((double *) ptr);
13 printf(“%f\n”,y);
14
15 int c = *((int *) ptr);
16 printf(“%d\n”,c);
17
18 return 0;
19 }
8
Answers: Void Pointers
> cat -n void_pointer.c
1 // Demonstrate void pointer dereferencing and the associated
2 // shenanigans. Compiler needs to be convinced to dereference in most
3 // cases and circumventing the type system (compiler’s ability to
4 // check correctness) is fraught with errors.
5 #include
6 int main(){
7 int a = 5;
8 double x = 1.2345;
9 void *ptr;
10
11 ptr = &a;
12 int b = *((int *) ptr);
13 printf(“%d\n”,b);
14
15 ptr = &x;
16 double y = *((double *) ptr);
17 printf(“%f\n”,y);
18
19 int c = *((int *) ptr);
20 printf(“%d\n”,c);
21
22 return 0;
23 }
> gcc void_pointer.c > ./a.out
5
1.234500
// caste to convince compiler to deref
// caste to convince compiler to deref // kids: this is why types are useful
309237645 # interpreting floating point bits as an integer
// int
// double
// pointer to anything
9
Byte-level Picture of Memory at main() line 20
|——-+—–+——–+———–+——+————| |BYTE | |VALUE |VALUE | VAL| intVALUE| |ADDR |SYM|TYPED |BINARY | HEX|inDECIMAL| |——-+—–+——–+———–+——+————|
|#2043|ptr|v |#2042|ptr|v |#2041|ptr|v |#2040|ptr|v |#2039|ptr|v |#2038|ptr|v |#2037|ptr|v
| #2036 | ptr | #2028
|00000000|0x00|
|00000000|0x00|
|00000000|0x00|
|00000000|0x00|
|00000000|0x00|
|00000000|0x00|
|00000111|0x07|
|11101100|0xec|
|00111111|0x3f|
|11110011|0xf3|
|11000000|0xc0| |10000011|0x83|1072939139|butptrpointsto
|00010010|0x12| |01101110|0x6e| |10010111|0x97| |10001101|0x8d| |00000000|0x00| |00000000|0x00| |00000000|0x00| |00000101|0x05| 5|
|#2035|x
|#2034|x
|#2033|x
|#2032|x
|#2031|x
|#2030|x
|#2029|x
|#2028|x
|#2027|a
|#2026|a
|#2025|a
|#2024|a |——-+—–+——–+———–+——+————|
|v
|v
|v
|v
|v
|v
|v
| 1.2345 |v
| double x occupies
| 8 contiguous bytes
| from #2028-#2035
|v |v |5
0
| void *ptr occupies
| 8 contiguous bytes
| from #2036-#2043
| and currently points
| at #2028; the bits/bytes
| ther must be caste in
| order to dereference
2028 |
| #2028 and prints bytes
| #2028-2031 as a 4-byte
| integer
| 4 contiguous bytes
| from #2024-#2027
309237645 |
| int a occupies
10
Answers: Void Pointers
▶ The big weird integer 309237645 printed at the end is because…
▶ ptr points at a memory location with a double
▶ The compiler is “tricked” into treating this location as storing
int data via the (int *) caste
▶ Integer vs Floating layout is very different, we’ll see this later
▶ Compiler generates low level instructions to move 4 bytes of
the double data to an integer location
▶ Both size and bit layout don’t match
▶ Since this is possible to do on a machine C makes it possible
▶ This does not mean it is a good idea: void_pointer.c illustrates weird code that usually doesn’t show up
▶ Avoid void * pointers when possible, take care when you must use them (there are many times you must use them in C)
11
But wait, there’re more types…
Unsigned Variants
Trade sign for larger positives
Fixed Width Variants since C99
Specify size / properties
Name
unsigned char
unsigned short
unsigned int
unsigned long
Range
0 to 255
0 to 65,535
0 to 4,294,967,295 0 to… big, okay?
int8_t int16_t int32_t int64_t int_fast8_t int_fast16_t int_fast32_t int_fast64_t int_least8_t int_least16_t int_least32_t int_least64_t intmax_t intptr_t uint8_t uint16_t uint32_t uint64_t uint_fast8_t uint_fast16_t uint_fast32_t uint_fast64_t uint_least8_t uint_least16_t uint_least32_t uint_least64_t uintmax_t uintptr_t
signed integer type with width of
exactly 8, 16, 32 and 64 bits respectively
fastest signed integer type with width of at least 8, 16, 32 and 64 bits respectively
smallest signed integer type with width of at least 8, 16, 32 and 64 bits respectively
maximum width integer type
integer type capable of holding a pointer unsigned integer type with width of exactly 8, 16, 32 and 64 bits respectively
fastest unsigned integer type with width of at least 8, 16, 32 and 64 bits respectively
smallest unsigned integer type with width of at least 8, 16, 32 and 64 bits respectively
maximum width unsigned integer type unsigned int capable of holding pointer
After our C crash course, we will discuss representation of integers with bits and relationship between signed / unsigned integer types
12
Arrays in C
▶ Array: a continuous block of homogeneous data
▶ Automatically allocated by the compiler/runtime with a fixed size 1
▶ Support the familiar [ ] syntax
{
}
|Addr |Type|Sym | Val| |——-+——+——+——-| |#4948|int*|ap |#4936| |#4944|int |a[2]| 30| |#4940|int |a[1]| 20| |#4936|int |a[0]| 10| |#4928|int*|p |#4924| |#4924|int |x | 42|
▶ Refer to a single element via arr[3]
▶ Bare name arr is the memory address where array starts
1 Modern C supports variable sized arrays in the stack but we will not use them.
int x
int *p
int a[3] = {10,20,30}; int*ap =a;
= 42; = &x;
13
Arrays and Pointers are Related with Subtle differences
Property
Declare like…
Refers to a… Which could be.. Location ref is Location…
Has at it..
Brace index? Dereference? Arithmetic? Assign to array? Interchangeable
Tracks num elems
Pointer
int *p; // rand val int *p = &x;
int *p = q;
Memory location Anywhere
Changeable
Assigned by coder One or more thing Yep: int z = p[0]; Yep: int y = *p; Yep: p++;
Yep: int *p = a; doit_a(int a[]); int *p = … doit_a(p);
NOPE
Nada, nothin, nope
Array
int a[5]; // rand vals int a[] = {1, 2, 3}; int a[2] = {2, 4}; Memory location
Fixed location
Not changeable
Determined by compiler
One or more thing
Yep: int z = a[0];
Nope
Nope
Nope
doit_p(int *p);
int a[] = {1,2,3}; doit_p(a);
NOPE
No a.length or length(a)
14
Example: pointer_v_array.c
1 // Demonstrate equivalence of pointers and arrays
2 #include
3 void print0_arr(int a[]){ // print 0th element of a 4 printf(“%ld: %d\n”,(long) a, a[0]); // address and 0th elem 5}
6 void print0_ptr(int *p){ // print int pointed at by p 7 printf(“%ld: %d\n”,(long) p, *p); // address and 0th elem 8}
9 int main(){
10 int *p = NULL;
11 printf(“%ld: %ld\n”,
12 (long) &p, (long)p);
13 int x = 21;
14 p = &x;
15 print0_arr(p);
16 int a[] = {5,10,15};
17 print0_ptr(a);
18 //a = p;
19 p = a;
20 print0_ptr(p);
21 return 0;
22 }
// declare a pointer, points nowhere
// print address/contents of p
// by casting to 64 bit long
// declare an integer
// point p at x
// pointer as array
// declare array, auto size
// array as pointer
// can’t change where array points
// point p at a
15
Execution of Code/Memory 1
9
10
11
<1> 12
<2> 13
<3> 14
15
16
17
<4> 18
<5> 19
20
21 }
int *p = NULL;
printf(“%ld: %ld\n”,
(long) &p, (long)p);
int x = 21;
p = &x;
print0_arr(p);
int a[] = {5,10,15};
print0_ptr(a);
//a = p;
p = a;
print0_ptr(p);
return 0;
|? |? | | a[2] | ? | |a[1]|? | |a[0]|? |
1 #include
2 void print0_arr(int a[]){
3 printf(“%ld: %d\n”,(long) a, a[0]); 4}
5 void print0_ptr(int *p){
6 printf(“%ld: %d\n”,(long) p, *p); 7}
8 int main(){
Memory at indicated
<1>
| Addr | Type | Sym | Val |
|——-+——+——+—–|
| #4948 | ?
| #4944 | int
| #4940 | int
| #4936 | int |#4928|int*|p |NULL| |#4924|int|x|?| <3>
|Addr |Type|Sym |Val | |——-+——+——+——-| |#4948|? |? |? | |#4944|int |a[2]|? | |#4940|int |a[1]|? | |#4936|int |a[0]|? | |#4928|int*|p |#4924|* |#4924|int |x |21 |
16
Execution of Code/Memory 2
9
10
11
<1> 12
<2> 13
<3> 14
15
16
17
<4> 18
<5> 19
20
21 }
int *p = NULL;
printf(“%ld: %ld\n”,
(long) &p, (long)p);
int x = 21;
p = &x;
print0_arr(p);
int a[] = {5,10,15};
print0_ptr(a);
//a = p;
p = a;
print0_ptr(p);
return 0;
|? |? | | a[2] | 15 |* |a[1]|10 |* |a[0]|5 |*
1 #include
2 void print0_arr(int a[]){
3 printf(“%ld: %d\n”,(long) a, a[0]); 4 }
5 void print0_ptr(int *p){
6 printf(“%ld: %d\n”,(long) p, *p); 7}
8 int main(){
Memory at indicated
<4>
| Addr | Type | Sym | Val |
|——-+——+——+——-|
| #4948 | ?
| #4944 | int
| #4940 | int
| #4936 | int |#4928|int*|p |#4924| |#4924|int |x |21 | <5>
|Addr |Type|Sym |Val | |——-+——+——+——-| |#4948|? |? | ?| |#4944|int |a[2]|15 | |#4940|int |a[1]|10 | |#4936|int |a[0]|5 | |#4928|int*|p |#4936|* |#4924|int |x |21 |
17
Summary of Pointer / Array Relationship Arrays
▶ Arrays are allocated by the Compiler at a fixed location ▶ Bare name a references is the starting address of the array ▶ Must use square braces a[i] to index into them
Pointers
▶ Pointers can point to anything, can change, must be manually directed
▶ Can use square braces p[i] or deref *p to index into them Interchangeability
▶ In most cases, functions that require an array can be passed a pointer
▶ Vice versa: requires a pointer can be passed an array BECAUSE array variables are treated as the starting memory address of the array data
18
Exercise: Pointer Arithmetic
“Adding” to a pointer increases the position at which it points:
▶ Add 1 to an int*: point to the next int, add 4 bytes
▶ Add 1 to a double*: point to next double, add 8 bytes Examine pointer_arithmetic.c below. Show memory contents and what’s printed on the screen
<1> 11
12
<2> 13
14
<3> 15
16
<4> 17
p=a+1; print0_ptr(p); p++; print0_ptr(p); p+=2; print0_ptr(p); return 0;
1 #include
2 void print0_ptr(int *p){
3 printf(“%ld: %d\n”,(long) p, *p); 4}
5 int main(){
6 intx=21;
7 int *p;
8 int a[] = {5,10,15};
9 p=a;
10 print0_ptr(p);
<1>
|Addr |Type|Sym | Val| |——-+——+——+——-| |#4948|? |? | ?| |#4944|int |a[2]| 15| |#4940|int |a[1]| 10| |#4936|int |a[0]| 5| |#4928|int*|p |#4936| |#4924|int |x | 21|
SCREEN:
4936 5
<2> ???
<3> ???
<4> ???
18 }
19
Answers: Pointer Arithmetic
5 6 7 8 9
10
<1> 11
12
<2> 13
int main(){ intx=21;
int *p;
int a[] = {5,10,15}; p=a; print0_ptr(p); p=a+1; print0_ptr(p);
<3>
|Addr |Type|Sym | Val|SCREEN: |——-+——+——+——-| 4936 5 |#4948|? |? | ?|494010 |#4944|int |a[2]| 15|494415 |#4940|int |a[1]| 10| |#4936|int |a[0]| 5| |#4928|int*|p |#4944| |#4924|int |x | 21|
SCREEN:
<3> 15 16
<4> 17
18 }
p+=2;
print0_ptr(p);
return 0;
<4>
|Addr |Type|Sym | Val| |——-+——+——+——-|
p++;
14 print0_ptr(p);
<2>
|Addr |Type|Sym | |——-+——+——+——-| 4936 5 |#4948|? |? | ?|494010 |#4944|int |a[2]| 15| |#4940|int |a[1]| 10| |#4936|int |a[0]| 5| |#4928|int*|p |#4940| |#4924|int |x | 21|
15 | 4952 ??? 10|
Val|SCREEN:
|#4952|? |? | |#4948|? |? | |#4944|int |a[2]| |#4940|int |a[1]| |#4936|int |a[0]| |#4928|int*|p |#4952| |#4924|int |x | 21|
4936 5 ? | 4940 10 ?|494415
5|
20
Pointer Arithmetic Alternatives
Pointer arithmetic often has more readable alternatives
printf(“enter 5 doubles\n”);
double arr[5];
for(int i=0; i<5; i++){
// POINTER: ick
scanf("%lf", arr+i);
}
printf("you entered:\n");
for(int i=0; i<5; i++){
OR
// PREFERRED
scanf("%lf", &arr[i]);
// PREFERRED
printf("%f ",arr[i]);
}
// POINTER: ick
printf("%f ", *(arr+i)); OR
But not always: following uses pointer arithmetic to append strings
char name[128];
printf("first name: ");
scanf(" %s", name);
int len = strlen(name);
name[len] = ' ';
printf("last name: ");
scanf(" %s",name+len+1);
printf("full name: %s\n",name);
// up to 128 chars
// read into name
// compute length of string
// replace \0 with space
// read last name at offset
See read_name.c to experiment
21
read_name.c : String Functions + Pointer Arithmetic
INITIAL MEMORY |...||
|#1038|?| |#1037|?| |#1036|?| |#1035|?| |#1034|?| |#1033|?| |#1032|?| |#1031|?| |#1030|?| |#1029|?| |#1028|?| |#1027|?| |#1026|?| |#1025|?|
name|#1024|?| len |#1020|?|
STEP 1
scanf(" %s", name); // Enters 'Chris' len = strlen(name);
|... | | |#1038|? | |#1037|? | |#1036|? | |#1035|? | |#1034|? | |#1033|? | |#1032|? | |#1031|? | |#1030|? | |#1029|'\0'| |#1028|'s'| |#1027|'i'| |#1026|'r'| |#1025|'h'|
name|#1024|'C' | len |#1020|5 |
Initial scanf() + strlen()
STEP 2
name[len] = ' '; |... | |
|#1038|? | |#1037|? | |#1036|? | |#1035|? | |#1034|? | |#1033|? | |#1032|? | |#1031|? | |#1030|? | |#1029|'' | | #1028 | 's' | | #1027 | 'i' | | #1026 | 'r' | | #1025 | 'h' |
name|#1024|'C' | len |#1020|5 |
Overwrite null char with a space
STEP 3
scanf(" %s", name+len+1); // Enter 'Kauffman'
|... | | | #1038 | '\0' | | #1037 | 'n' | | #1036 | 'a' | | #1035 | 'm' | | #1034 | 'f' | | #1033 | 'f' | | #1032 | 'u' | | #1031 | 'a' | | #1030 | 'K' | | #1029 | ' ' | | #1028 | 's' | | #1027 | 'i' | | #1026 | 'r' | | #1025 | 'h' |
name|#1024|'C' | len |#1020|5 |
Read in after space using scanf()
22
Allocating Memory with malloc() and free()
Dynamic Memory
▶ Most C data has a fixed size: single vars or arrays with sizes specified at compile time
▶ malloc(nbytes) is used to manually allocate memory
▶ single arg: number of bytes of memory
▶ frequently used with sizeof() operator
▶ returns a void* to bytes found or NULL if not enough space could be allocated
▶ free() is used to release memory
malloc_demo.c
#include
#include
int main(){
printf(“how many ints: “);
int len;
scanf(” %d”,&len);
int *nums = malloc(sizeof(int)*len);
printf(“initializing to 0\n”);
for(int i=0; i
# run program through valgrind
> valgrind ./a.out
==12676== Memcheck, a memory error detector
==12676== Copyright (C) 2002-2013, and GNU GPL’d, by et al.
==12676== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==12676== Command: a.out
==12676==
Uninitialized memory
==12676== Conditional jump or move depends on uninitialised value(s)
==12676== at 0x4005C1: main (badmemory.c:7)
==12676==
==12676== Conditional jump or move depends on uninitialised value(s)
==12676==
==12676==
==12676==
…
at 0x4E7D3DC: vfprintf (in /usr/lib/libc-2.21.so)
by 0x4E84E38: printf (in /usr/lib/libc-2.21.so)
by 0x4005D6: main (badmemory.c:8)
Link: Description of common Valgrind Error Messages
30
Exercise: free()’ing in the Wrong Spot
Common use for malloc() is for one function to allocate memory
and return its location to another function (such as in A1). Question becomes when to free() such memory.
Program to the right is buggy, produces following output on one system
> gcc free_twice.c
> ./a.out
ones[0] is 0
ones[1] is 0
ones[2] is 1
ones[3] is 1 13
ones[4] is 1
▶ Why does this bug happen? ▶ How can it be fixed?
▶ Answers in free_twice.c
1
2 #include
3 #include
4
5 int* ones_array(int len){
6 int *arr = malloc(sizeof(int)*len); 7 for(int i=0; i
ones[0] ones[1] ones[2] ones[3] ones[4] free(): Aborted
9 int *ones_array(int len){
10 int *arr = malloc(sizeof(int)*len);
11 for(int i=0; i
==10125== Memcheck, a memory error detector
…
==10125== Invalid free() 23 ==10125== at 0x48399AB: free
==10125== by 0x10921A: main (free_twice.c:24)
24 free(ones); // 2nd free 25 return 0;
26 }
Note that the Valgrind output gives an exact line number where the problem occurs but this is not the line to change to fix the problem.
32
18 int main(){
19 int *ones = ones_array(5);
20 for(int i=0; i<5; i++){
21 printf("ones[%d] is %d\n",i,ones[i]); 22 }
Answers: free()’ing in the Wrong Spot
33
structs: Heterogeneous Groupings of Data
▶ Arrays are homogenous: all elements the same type
▶ structs are C’s way of defining heterogenous data
▶ Each field can be a different kind
▶ One instance of a struct has all
fields
▶ Access elements with ’dot’ notation
▶ Several syntaxes to declare, we’ll favor modern approach
▶ Convention: types have _t at the end of their name to help identify them (not a rule but a good idea)
typedef struct{ // declare type
int an_int;
double a_doub;
char the_car;
int my_arr[6];
} thing_t;
thing_t a_thing;
a_thing.an_int
a_thing.a_doub
a_thing.the_char
a_thing.my_arr[2] = 7;
int i = a_thing.an_int;
thing_t b_thing = {
.an_int = 15,
.a_doub = 19.2,
.the_char = 'D',
.my_arr = {17, 27, 37,
47, 57, 67}
};
// variable
= 5;
= 9.2; = 'c';
// variable
// initialize
// all fields
34
struct Ins/Outs Recursive Types
▶ structs can have pointers to their same kind
▶ Syntax is a little wonky
vvvvvvvvvvv
typedef struct node_struct {
char data[128];
struct node_struct *next;
^^^^^^^^^^^
} node_t;
Arrow Operator
▶ Pointer to struct, want to work with a field
▶ Use ’arrow’ operator -> for this (dash/greater than)
Dynamically Allocated Structs
▶ Dynamic Allocation of structs requires size calculation
▶ Usesizeof()operator
node_t *one_node =
malloc(sizeof(node_t));
int length = 5;
node_t *node_arr =
malloc(sizeof(node_t) * length);
node_t *node = …;
if(node->next == NULL){ … }
list_t *list = …;
list->size = 5;
list->size++;
35
Exercise: Structs in Memory
▶ Structs allocated in memory
are laid out compactly
▶ Compiler may pad fields to place them at nice alignments (even addresses or word boundaries)
typedef struct {
double x;
int y;
char nm[4];
} small_t;
int main(){
small_t a =
{.x=1.23, .y=4, .nm=”hi”};
small_t b =
{.x=5.67, .y=8, .nm=”bye”};
}
Memory layout of main()
|Addr |Type |Sym |Val | |——-+——–+———+——| |#1031|char |b.nm[3]|\0 | |#1030|char |b.nm[2]|e | |#1029|char |b.nm[1]|y | |#1028|char |b.nm[0]|b | |#1024|int |b.y |8 | |#1016|double|b.x |5.67| |#1015|char |a.nm[3]|? | |#1014|char |a.nm[2]|\0 | |#1013|char |a.nm[1]|i | |#1012|char |a.nm[0]|h | |#1008|int |a.y |4 | |#1000|double|a.x |1.23|
Result of?
scanf(“%d”, &a.y); // input 7
scanf(“%lf”, &b.x); // input 9.4
scanf(“%s”, b.nm); // input yo
36
Answers: Structs in Memory
scanf(“%d”, &a.y); // input 7
scanf(“%lf”, &b.x); // input 9.4
scanf(“%s”, b.nm); // input yo
| | | |Val|Val| |Addr |Type |Sym |Before|After| |——-+——–+———+——–+——-| |#1031|char |b.nm[3]|\0 | | |#1030|char |b.nm[2]|e |\0 | |#1029|char |b.nm[1]|y |o | |#1028|char |b.nm[0]|b |y | |#1024|int |b.y |8 | | |#1016|double|b.x |5.67 |9.4 | |#1015|char |a.nm[3]|? | | |#1014|char |a.nm[2]|\0 | | |#1013|char |a.nm[1]|i | | |#1012|char |a.nm[0]|h | | |#1008|int |a.y |4 |7 | |#1000|double|a.x |1.23 | |
37
Structs: Dots vs Arrows
Newcomers wonder when to use Dots vs Arrows
▶ Use Dot (s.field) with an Actual struct
▶ Use Arrow (p->field) for a Pointer to a struct
small_t small;
small_t *sptr;
sptr = &small;
small.x = 1.23;
sptr->x = 4.56;
(*sptr).x = 4.56;
small.y = 7;
sptr->y = 11;
// struct: 16 bytes
// pointer: 8 bytes
// point at struct
// actual struct
// through pointer
// ICK: not preferred
// actual struct
// through pointer
Memory at end of code on left
|Addr |Sym |Value| |——-+————-+——-|
small.nm[0] = ‘A’;
sptr->nm[1] = ‘B’;
sptr->nm[2] = ‘\0’; // through pointer
// through struct
// through pointer
| #2061 | small.nm[1]
| #2060 | small.nm[0]
| #2056 | small.y
| #2048 | small.x
|B | |A | | 11 | | 4.56 |
| #2072 | …
| #2064 | sptr
| #2063 | small.nm[3]
| #2062 | small.nm[2] | \0 |
| … | | #2048 | |? |
38
read_structs.c: malloc() and scanf() for structs
1 // Demonstrate use of pointers, malloc() with structs, scanning
2 // structs fields
3
4 #include
5 #include
7 typedef struct {
// simple struct char nm[4];
8 double x;
9 } small_t;
10
int y;
11 int main(){
12 small_t c;
13 small_t *cp =
14 scanf(“%lf %d
15 printf(“%f %d
16
17 small_t *sp =
18 scanf(“%lf %d
19 printf(“%f %d
20
21 small_t *sarr
22 for(int i=0; i<5; i++){
23 scanf("%lf %d %s", &sarr[i].x, &sarr[i].y, sarr[i].nm); // read
24 printf("%f %d %s\n", sarr[i].x, sarr[i].y, sarr[i].nm); // print
25 }
26
27 free(sp);
28 free(sarr);
29 return 0;
30 }
&c;
%s", &cp->x, &cp->y, cp->nm); %s\n”,cp->x, cp->y, cp->nm);
// stack variable
// address of stack var
// read struct fields
// print struct fields
malloc(sizeof(small_t)); %s”, &sp->x, &sp->y, sp->nm); %s\n”,sp->x, sp->y, sp->nm);
// malloc’d struct
// read struct fields
// print struct fields
= malloc(5*sizeof(small_t));
// malloc’d struct array
// free single struct // free struct array
39
File Input and Output
▶ Standard C I/O functions for reading/writing file data.
▶ Work with text data: formatted for human reading
FILE *fopen(char *fname, char *mode);
// open file named fname, mode is “r” for reading, “w” for writing
// returns a File Handle (FILE *) on success
// returns NULL if not able to open file; do not fclose(NULL)
int fclose(FILE *fh);
// close file associated with fh, writes pending data to file,
// free()’s memory associated with open file
// Do not fclose(NULL)
int fscanf(FILE *fh, char *format, addr1, addr2, …);
// read data from an open file handle according to format string
// storing parsed tokens in given addresses returns EOF if end of file
// is reached
int fprintf(FILE *fh, char *format, arg1, arg2, …);
// prints data to an open file handle according to the format string
// and provided arguments
void rewind(FILE *fh);
// return the given open file handle to the beginning of the file.
Example of use in struct_text_io.c
40
Binary Data I/O Functions
▶ Open/close files same way with fopen()/fclose()
▶ Read/write raw bytes (not formatted) with the following
size_t fread(void *dest, size_t byte_size, size_t count, FILE *fh);
// read binary data from an open file handle. Attempt to read
// byte_size*count bytes into the buffer pointed to by dest.
// Returns number of bytes that were actually read
size_t fwrite(void *src, size_t byte_size, size_t count, FILE *fh);
// write binary data to an open file handle. Attempt to write
// byte_size*count bytes from buffer pointed to by src.
// Returns number of bytes that were actually written
See examples of use in struct_binary_io.c Tradeoffs between Binary and Textual Files
▶ Binary files usually smaller than text and can be directly read into memory but NOT easy on the eyes
▶ Text data more readable but more verbose, must be parsed and converted to binary numbers
41
Strings are Character Arrays Conventions
▶ Convention in C is to use character arrays as strings
▶ Terminate character arrays with the \0 null character to indicate their end
char str1[6] =
{‘C’,’h’,’r’,’i’,’s’,’\0′};
▶ Null termination done by compiler for string constants
char str2[6] = “Chris”;
// is null terminated
▶ Null termination done by most standard library functions like scanf()
Be aware
▶ fread() does not append nulls when reading binary data
▶ Manually manipulating a character array may overwrite ending null
String Library
▶ Include with
▶ Null termination expected
▶ strlen(s): length of string
▶ strcpy(dest, src): copy chars from src to dest
▶ Limited number of others
42
Exercise: Common C operators
Arithmetic Comparison Logical Memory Compound Bitwise Ops Conditional
Bitwise Ops
+ – * / %
== > < <= >= != && || !
& and *
+= -= *= /= … ^|&~
? :
Integer/Floating Division
Predict values for each variable
int q = 9 / 4;
int r = 9 % 4;
double x = 9 / 4;
double y = (double) 9 / 4;
double z = ((double)9) / 4;
double w = 9.0 / 4;
double t = 9 / 4.0;
int a=9, b=4;
double t = a / b;
Conditional (ternary) Operator
double x = 9.95; inty=(x<10.0)?2:4; 43
Will discuss soon
int x = y << 3;
int z = w & t;
long r = x | z;
Answers: Integer vs Floating Division
Integer versus real division: values for each of these are...
int q = 9 / 4;
int r = 9 % 4;
double x = 9 / 4;
double y = (double) 9 / 4;
double z = ((double)9) / 4;
double w = 9.0 / 4;
double t = 9 / 4.0;
int a=9, b=4;
double t = a / b;
// quotient 2
// remainder 1
// 2.0 (int quotient first)
// 2.25
// 2.25
// 2.25
// 2.25
// 2 (int quotient)
44
C Control Structures
Looping/Iteration
// while loop
while(truthy){
stuff;
more stuff; }
// for loop
for(init; truthy; update){
stuff;
more stuff; }
// do-while loop
do{
stuff;
more stuff;
} while( truthy );
Conditionals
// simple if
if( truthy ){
stuff;
more stuff; }
// chained exclusive if/elses
if( truthy ){
stuff;
more stuff; }
else if(other){
stuff;
} else{
stuff;
more stuff; }
// ternary ? : operator
int x = (truthy) ? yes : no;
45
Jumping Around in Loops break: often useful
// break statement ends loop
// only valid in a loop
while(truthy){
stuff;
if( istrue ){
something;
break;-----+ }| more stuff; |
}| after loop; <--+
// break ends inner loop,
// outer loop advances
for(int i=0; i<10; i++){
for(int j=0; j<20; j++){
printf("%d %d ",i,j);
if(j == 7){
break;-----+ }| }| printf("\n");<-+
}
continue: occasionally useful
// continue advances loop iteration
// does update in for loops
+------+
V| for(int i=0; i<10; i++){ | printf("i is %d\n",i); | if(i % 3 == 0){ | continue;-------------+
}
printf("not div 3\n");
}
Prints
i is 0
i is 1
not div 3
i is 2
not div 3
i is 3
i is 4
not div 3
... 46
Really Jumping Around: goto
▶ Machine-level control involves jumping to different instructions
▶ C exposes this as ▶ somewhere:
1 // Demonstrate control flow with goto
2 // Low level assembly jumps are similar
3 #include
4 int main(){
5 int i=0;
6 beginning:
7 printf(“i is %d\n”,i);
8 i++;
9 if(i < 10){
10 goto beginning; // go back
11 }
12 goto ending; // go forward
13 printf("print me please!\n");
14 ending: // label for goto
15 printf("i ends at %d\n",i);
16 return 0;
17 }
▶
label for code position
▶ goto somewhere; jump to that location
goto_demo.c
demonstrates a loop with gotos
▶ Avoid goto unless you have a compelling motive
▶ Beware spaghetti code... and raptor attacks...
// label for gotos
XKCD #292
47
switch()/case: the worst control structure
▶ switch/case allows jumps based on an integral value
▶ Frequent source of errors
▶ switch_demo.c shows some
features
1 // Demonstrate peculiarities of switch/case 2 #include
3 int main(){
4 while(1){
5 printf(“enter a char: “); 6 char c;
▶
▶ fall through cases
// switch on // entered j line\n”); //gotoend // entered a
read char of switch
use of break default catch-all
10 printf(“Down
11 break;
12 case ‘a’:
13 printf(“little a\n”);
14 case ‘A’: // entered A 15 printf(“big A\n”);
▶
▶ Use in a loop
▶ May enable some small compiler optimizations
▶ Almost never worth correctness risks: one good use in my experience
▶ Favorif/elseif/else unless compelled otherwise
16 printf(“append mode\n”); 17 break; // go to end 18 case ‘q’: // entered q 19 printf(“Quitting\n”);
of switch
7 scanf(” %c”,&c); 8 switch(c){
9 case ‘j’:
// ignore preceding spaces
20 return 0; // return from main
21 default: // entered anything else 22 printf(“other ‘%c’\n”,c);
23 break; 24 }
25 }
26 return 0; 27 }
// go to end of switch // end of switch
48
A Program is Born: Compile, Assemble, Link, Load
▶ Write some C code in program.c
▶ Compile it with toolchain like GNU Compiler Collection
gcc -o program prog.c
▶ Compilation is a multi-step process
▶ Check syntax for correctness/errors
▶ Perform optimizations on the code if possible
▶ Translate result to Assembly Language for a specific target
processor (Intel, ARM, Motorola)
▶ Assemble the code into object code, binary format (ELF)
which the target CPU understands
▶ Link the binary code to any required libraries (e.g. printing) to
make an executable
▶ Result: executable program, but…
▶ To run it requires a loader: program which copies executable into memory, initializes any shared library/memory references required parts, sets up memory to refer to initial instruction
49
Review Exercise: Memory Review
1. How do you allocate memory on the Stack? How do you
de-allocate it?
2. How do you allocate memory dynamically (on the Heap)? How do you de-allocate it?
3. What other parts of memory are there in programs?
4. How do you declare an array of 8 integers in C? How big is it
and what part of memory is it in?
5. Describe several ways arrays and pointers are similar.
6. Describe several ways arrays and pointers are different.
7. Describe how the following two arithmetic expressions differ.
int x=9, y=20;
int *p = &x;
x = x+1;
p = p+1;
50
Answers: Memory Review
1. How do you allocate memory on the Stack? How do you de-allocate it? Declare local variables in a function and call the function. Stack frame
has memory for all locals and is de-allocated when the function finishes/returns.
2. How do you allocate memory on the Heap? How do you de-allocate it? Make a call to ptr = malloc(nbytes) which returns a pointer to the requested number of bytes. Call free(ptr) to de-allocate that memory.
3. What other parts of memory are there in programs?
Global area of memory has constants and global variables. Text area has binary assembly code for CPU instructions.
4. How do you declare an array of 8 integers in C? How big is it and what part of memory is it in?
An array of 8 ints will be 32 bytes big (usually).
On the stack: int arr[8]; De-allocated when function returns.
On the heap: int *arr = malloc(sizeof(int) * 8); Deallocated with free(arr);
51
Answers: Memory Review
5. Describe several ways arrays and pointers are similar.
Both usually encoded as an address, can contain 1 or more items, may
use square brace indexing like arr[3] = 17; Interchangeable as arguments to functions. Neither tracks size of memory area referenced.
6. Describe several ways arrays and pointers are different.
Pointers may be deref’d with *ptr; can’t do it with arrays. Can change
where pointers point, not arrays. Arrays will be on the Stack or in Global Memory, pointers may also refer to the Heap.
7. Describe how the following two arithmetic expressions differ.
int x=9, y=20;
int *p = &x;
x = x+1;
p = p+1;
// x at #1024
// p hold VALUE #1024 (points at x)
// x is now 10: normal arithmetic
// p is now #1028: pointer arithmetic
// may or may not point at y
52