Course review Exam topics

System programming

Pipes • The byte stream written to one end of the pipe can be read from the other • Once created, pipes are referenced by file descriptor handles • Pipes are accessible only by related processes • No identifier is used to rendez-vous on pipes, they are requested directly to the kernel • Pipes are process-persistent: they disappear when related processes terminate

Pipes user process

fd [0]

fd [1]

user process

fd [0]

fd [1]

pipe kernel

• Every read from a pipe copy from kernel space to user space • Every write to a pipe copy from user space to kernel space

Pipe usage • Pipes become useful by exploiting the fact that file descriptors are inherited through fork. Half-duplex pipe recipe 1. pipe(fds) 2. fork() 3. parent: close(fds[0]) 4. child: close(fds[1]) 5. parent can transfer data to child with write(fds[1], ...) 6. child can receive data from parent with read(fds[0], ...) (exchange 0 and 1 for child to parent data transfer)

Pipe and fork parent

child fork

fd [0]

fd [0]

fd [1]

pipe kernel

fd [1]

Pipe and fork parent

child fork fd [0]

fd [1]

pipe kernel

Close unused ends • The unused ends of a pipe are usually closed before starting to use a pipe. • Performing I/O on a pipe with closed end behaves as follows: • Read from a pipe whose write end is closed returns 0 intuition: indicate there is nothing else to read; 0 is the standard way of read to signal end-of-file

• Write to a pipe whose read end is closed returns -1 (error)

What does this program do? int main( void ) { int n, fd [ 2 ] ; pid_t pid ; char line [MAXLINE ] ; pid = fork ( ); i f ( pipe ( fd ) < 0) perror ( " pipe error " ) ; i f ( pid > 0) { /* parent */ close ( fd [ 0 ] ) ; write ( fd [1] , " Hello , World ! \n" , 14) ; } else { /* child */ close ( fd [ 1 ] ) ; n = read ( fd [0] , line , MAXLINE ) ; write (STDOUT_FILENO, line , n ) ; } exit ( EXIT_SUCCESS ) ; }

Is there something wrong with it? int main( void ) { int n, fd [ 2 ] ; pid_t pid ; char line [MAXLINE ] ; pid = fork ( ); i f ( pipe ( fd ) < 0) perror ( " pipe error " ) ; i f ( pid > 0) { /* parent */ close ( fd [ 0 ] ) ; write ( fd [1] , " Hello , World ! \n" , 14) ; } else { /* child */ close ( fd [ 1 ] ) ; n = read ( fd [0] , line , MAXLINE ) ; write (STDOUT_FILENO, line , n ) ; } exit ( EXIT_SUCCESS ) ; }

Full-duplex communication with pipes

• Once more: pipes are half-duplex: one pipe can be used to transfer data in one direction only - either from parent to child or from child to parent • To do full-duplex communication with pipes (i.e. transfer data in both directions), 2 pipe calls before fork are needed Full-duplex pipe recipe 1. pipe(p2c); 2. pipe(c2p) 3. fork() 4. parent: close(p2c[0]); close(c2p[1]) 5. child: close(p2c[1]); close(c2p[0]) 6. parent ! child: write(p2c[1], ...) 7. child ! parent: write(c2p[1], ...)

After fork() 2 processes are asynchronous wait (&child_status); if (WIFEXITED (child_status)) printf ("the child process exited normally, with exit code %d\n", WEXITSTATUS (child_status)); else printf ("the child process exited abnormally\n");

Signals

Handling signals void my_handler(int signum) { const char msg[] = "Signal handler got signal\n"; write (STDOUT_FILENO, msg, sizeof msg); } int main(int argc, char *argv[]) { printf("PID: %d\n", getpid()); // Set up signal handler struct sigaction action; action.sa_handler = &my_handler; sigaction (SIGINT, &action, NULL); while (1) { pause(); } return 0;

What happens if we run this program (signals1) and hit Ctr+C several times?

How can we shutdown signals1 • We can’t kill it with Ctrl-C, we can use kill to shutdown ./signals1: $ kill 11152

• kill defaults to sending a SIGTERM

Ignoring signals • Ignoring • Blocking

Ignoring signals: not even receiving a signal int main(int argc, char *argv[]) {

printf("PID: %d\n", getpid()); // Set up signal handler struct sigaction action;

action.sa_handler = SIG_IGN; sigaction(SIGINT, &action, NULL); while (1) {

pause(); } return 0; }

What happens if we run this program (signals2) and hit Ctr+C several times?

Blocking signals temporarily • What if you want your program to handle any signals that come in, just do it later? • You might have a critical section where you don’t want to be interrupted, but afterwards you want to know what came in.

• You can block signals using sigprocmask

Blocking signals struct sigaction action; action.sa_handler = &my_handler; sigaction(SIGINT, &action, NULL); sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGINT); printf("Blocking signals...\n");

sigprocmask (SIG_BLOCK, &sigset, NULL); // Critical section sleep(5); printf("Unblocking signals...\n");

sigprocmask (SIG_UNBLOCK, &sigset, NULL);

Blocking signals with sigaction • One common use for this is blocking signals while the signal handler is running. • That way you can have your signal handler modify some non-atomic state (say, a counter of how many signals have come in) in a safe way • So sigaction takes a mask of signals it should block while the handler is executing

Blocking other signals while handler is running struct sigaction action; action.sa_handler = &my_handler; sigemptyset(&action.sa_mask); sigaddset(&action.sa_mask, SIGINT); sigaddset(&action.sa_mask, SIGTERM); sigaction(SIGINT, &action, NULL); • Here, we’re masking both SIGINT and SIGTERM: if either of these signals comes in while my_handler is running, they’ll be blocked until it completes.

Sockets

Client-Server communication • Server: • passively waits for and responds to clients • passive socket • Client: • initiates the communication • must know the address and the port of the server • active socket

Socket primitives Function

Meaning

socket

Create a new communication endpoint

bind

Attach a local address to a socket

listen

Announce willingness to accept connections

accept

Block caller until a connection request arrives

connect

Actively attempt to establish connection

send (write)

Send some data over the connection

receive (read)

Receive some data over the connection

close

Release the connection

Stream socket (TCP) Server

Client

socket()

socket()

bind() listen() accept()

connect()

recv()

send()

send()

recv()

close()

close()

Socket creation in C: int sockid= socket(family, type, protocol); • sockid: socket descriptor, an integer (like a file-handle) • family: integer, communication domain, e.g.,PF_INET, IPv4 protocols, Internet addresses (typically used) • type: communication type: SOCK_STREAM or SOCK_DGRAM • protocol: specifies protocol IPPROTO_TCP or IPPROTO_UDP (usually set to 0 - use default protocol) • upon failure returns -1 NOTE: socket() call does not specify where data will be coming from, nor where it will be going to –it just creates the interface!

Closing socket • When finished using a socket, the socket should be closed status= close(sockid); • sockid: the file descriptor (socket being closed) • status: 0 if successful, -1 if error

• Closing a socket closes a connection (for stream socket) and frees up the port used by the socket

Specifying Addresses • Socket API defines a generic data type for addresses: struct sockaddr { unsigned short sa_family; /* Address family (e.g. AF_INET) */ char sa_data[14]; /* Family-specific address information */ }

• Particular form of the sockaddr used for TCP/IPaddresses: struct in_addr { unsigned long s_addr; /* Internet address (32 bits) */ } struct sockaddr_in{ unsigned short sin_family; /* Internet protocol (AF_INET) */ unsigned short sin_port; /* Address port (16 bits) */ struct in_addr sin_addr; /* Internet address (32 bits) */ char sin_zero[8]; /* Not used */

}

Important: sockaddr_in can be casted to a sockaddr

Assign address to socket: int status = bind(sockid, &addrport, size); • associates and reserves a port for use by the socket

See prev. slide

• sockid: integer, socket descriptor • addrport: struct sockaddr, the (IP) address and port of the machine for TCP/IP server, internet address is usually set to INADDR_ANY, i.e., chooses any incoming interface • size: the size (in bytes) of the addrport structure

• status: upon failure -1 is returned

Example int sockid; struct sockaddr_in addrport; sockid= socket (PF_INET, SOCK_STREAM, 0); addrport.sin_family= AF_INET; addrport.sin_port= htons(5100); addrport.sin_addr.s_addr = htonl(INADDR_ANY); If (bind(sockid, (struct sockaddr *) &addrport, sizeof(addrport))!= -1)

Listening for connections: int status = listen(sockid, queueLimit); • Instructs TCP protocol implementation to listen for connections • sockid: integer, socket descriptor • queuelen: integer, # of active participants that can “wait”for a connection • status: 0 if listening, -1 if error • Important: listen() is non-blocking: returns immediately. • The listening socket (sockid) is never used for sending and receiving – it is used by the server only as a way to get new sockets

Establish connection (in client): int status=connect(sockid, &foreignAddr, addrlen); • The client establishes a connection with the server by calling connect() • sockid: integer, socket to be used in connection • foreignAddr: struct sockaddr: address of the passive participant (server) • addrlen: integer, sizeof(name) • status: 0 if successful connect, -1 otherwise • Important: connect() is blocking

Incoming connection: int s= accept(sockid, &clientAddr, &addrLen); • The server gets a socket for an incoming client connection by calling accept() • s: integer, the new socket (used for data-transfer) • sockid: integer, the orig. socket (being listened on) • clientAddr: struct sockaddr, address of the active participant - filled in upon return • addrLen: sizeof(clientAddr): value/result parameter - must be set appropriately before call. It is adjusted upon return • Important: accept() is blocking: waits for connection before returning • Dequeues the next connection on the queue for socket (sockid)

Exchanging data int count = write (sockid, msg, msgLen); int count = send(sockid, msg, msgLen, flags); • msg: message to be transmitted • msgLen: integer, length of message (in bytes) to transmit • count: # bytes transmitted (-1 if error) int count = read (sockid, recvBuf, bufLen); int count = recv(sockid, recvBuf, bufLen, flags); • Calls are blocking - returns only after data is sent / received

Summary • Client • Create a TCP socket • Establish connection • Communicate • Close the connection

• Server • Create a TCP socket • Assign a port to socket • Set socket to listen • Repeatedly: • Accept new connection • Communicate • Close the connection

Dealing with blocking calls • Many of the functions we saw block (by default) until a certain event: • • • •

accept: until a connection comes in connect: until the connection is established recv, read: until data is received send, write: until data are pushed into socket’s buffer

• For simple programs, blocking is convenient • What about more complex programs? • multiple connections • simultaneous sends and receives • simultaneously doing non-networking processing

Multiplexing • We may need to cope with multiple I/O channels e.g., supporting the service over multiple ports • Problem: from which socket the server should accept connections or receive messages? • Solution: select() • specifies a list of descriptors to check for pending I/O operations • blocks until one of the descriptors is ready • returns which descriptors are ready

Select int select (maxDescPlus1, &readDescs, &writeDescs, &exceptionDescs, &timeout); • maxDescsPlus1: integer, hint of the maximum number of descriptors • readDescs: fd_set, checked for immediate input availability • writeDescs: fd_set, checked for the ability to immediately write data • exceptionDescs: fd_set, checked for pending exceptions • timeout: struct timeval, how long it blocks (NULL means forever)

• Returns the total number of ready descriptors, -1 in case of error • On return changes the descriptor lists so that only the corresponding positions are set

Example: 1/2 while (running) { /* Zero socket descriptor vector and set for server sockets */ /* This must be reset every time select() is called */ FD_ZERO (&sockSet); FD_SET (STDIN_FILENO, &sockSet); /* Add keyboard to descriptor vector */ for (i = 0; i < N; i++) FD_SET (servSock [i], &sockSet); /* Timeout specification */ selTimeout.tv_sec= timeout; /* timeout (secs.) */ selTimeout.tv_usec= 0; /* 0 microseconds */ …

Example: 2/2 … /* Suspend program until descriptor is ready or timeout */ If (select (maxDescriptor + 1, &sockSet, NULL, NULL, &selTimeout) == 0) printf ("No requests for %ld secs...Server still alive\n", timeout); else { if (FD_ISSET(0, &sockSet)) { /* Check keyboard */ printf ("Shuttingdown server\n"); getchar(); running = 0; } for (i = 0; i < N; i++) if (FD_ISSET(servSock[i], &sockSet)) { printf("Request on socket %d: ", i); HandleTCPClient (AcceptTCPConnection(servSock[i])); } } }

Shell scripting

Shell script • The input language is in fact a complete programming language. • You can write sophisticated programs in "shell script".

Variables syntax I • In sh, you don't declare variables, you just start using them. • An assignment statement begins with the variable name, no space, an equals sign, again no space, and then the value to be assigned:

x=3 • To access the values of variables, we use a command-line substitution which begins with a '$‘:

echo x has the value $x • outputs: x has the value 3 • and similarly you can use shell variable substitutions as file names, or anything on a command line.

Variables syntax II • ${var} to avoid maximal munch problems • if you wanted the value of the variable x and then "blah", you couldn't write $xblah because that would refer to a variable named "xblah"; but you can write ${x}blah • ${var-defvalue} to supply a default value. If $var has not been assigned, this expands to "defvalue" instead of to the null string

expr • Utility program which evaluates various expressions. • For example, "expr 1 + 2" will output "3". • All of these parameters have to be different tokens, i.e. different elements of argv

• "x = x + 1" in a normal high-level programming language can be written in sh as: x=`expr $x + 1`

Command-line arguments • Command-line arguments are also accessed using variable substitutions: • $0 is the command name • $1 through $9 are the first nine command-line arguments

Any Unix command • Your sh program can use any unix command, including any useful little utilities you write in C or any other programming language. • It's quite common for a complex package to include some C code and some shell scripts, and the shell scripts invoke the C programs

What will be printed? 1 • What is the output (on stdout) from the following commands? expr 2 + 3 | wc -l

echo hi | wc -c

What will be printed? 2 (echo hi; echo bye) | sort

echo hi; echo bye | wc -l

Control constructs: if • Boolean values in sh are based on command exit status: "true" command in /bin is just exit 0; "false" is just exit 1. • Command "foo" succeeds or fails. if foo then bar else echo sorry, foo failed >&2 exit 1 fi

test • We need to be able to test a lot of things. Think of 'if' statements you use in C or java -- if x.forward

b.

echo 'To forward your mail to $address, type: echo $address >.forward'

c.

echo "To forward your mail to $address, type: echo $address >.forward"

Which version gives the desired output?

Piping and forking commands • There is a semi-colon command separator available, but it's not used as often as in C or java because usually a newline character separates commands. • There are also parentheses. The shell forks and runs the commands all together:

(a;b;c) | sort (echo This is foo.c; cat foo.c; echo This is bar.c; cat bar.c) | lpr

Globbing in shell scripts • command-line: "globbing" done by the shell. • '*' matches any number of any character. *.c *x*y

• '?' matches any one character. a?.pdf • [list of chars] a[1234].pdf • [range] a[1-4].pdf use [a-z] to match any lower-case letter combine them: [a-xz] matches any lower-case letter except 'y'

What will be printed? 3 expr 2 * 3 $ mkdir + $ expr 2 * 3 5 $

While loop • "while" – same evaluation as for "if"

• Example: loop which counts 1 to 10: i=0 while test $i -lt 10 do i=`expr $i + 1` echo $i done

For loop for i in hello goodbye do echo $i, world done

x=4 for i in `seq 9` do echo `expr $i * $i` done

What is printed here?

Exercises 1/3 • How to define variable x with value 10 and print it on screen?

• How to print sum of two numbers, let's say 6 and 3

Exercises 2/3 • How to define two variables x=20, y=5 and then to print division of x by y (i.e. x/y)

• Modify above and store division of x and y to variable called z

Exercises 3/3 • Point out error if any in following script myname=Vivek

myos = TroubleOS myno=5 echo "My name is $myname" echo "My os is $myos" echo "My number is myno, can you see this number"

Running shell scripts – without ./ • "path" variable: List of directories to be consulted when looking up commands specified without path names. • E.g. you type "cat", it execs "/bin/cat". It finds it by looking through the path, which is a list of directories including /bin.

sh: PATH=/bin:/usr/bin sh: PATH=/bin:/usr/bin:.

Running shell scripts – without .sh • Now, when we put it in a file, we want to be able to run it by saying the name of that file like any other unix command; we don't want to have to say ".sh". • In fact if we name our file as "file" and just turn on the 'executable' mode bit of the file by typing "chmod +x file", it will invoke sh on it when you run the program.

File Permissions chmod (change mode) • Changes the permissions (mode) on an existing inode (file, directory, etc.) ls -lid (list structure, long version, inode, directory) • Shows the permissions of an inode

Exercise 1: fill in the table

Row

Symbolic mode

Octal mode

User/Owner Group

Others

1

rwxrw-r-x

765

rwx

rx

2

r---wx-w-

3

--x------

rw

Exercise 2: another table • For each three-digit octal permission in the following table, give the equivalent nine-character symbolic permission and the three symbolic permissions that apply to each of User/Owner, Group, and Other Row

Octal mode

0

000

1

001

2

010

3

011

4

100

5

101

6

110

7

111

Symbolic mode

User/Owner

Group

Others

Directory permissions exercise A. From in the w12 directory change the permission of the top sub-directory using the chmod command given in the second column (Command line) of the table. Execute this chmod command in the w12 directory to set the permissions on the top subdirectory. B. Next, for the row and permission value you just set in Step A, try to execute the four commands listed across the top of the table. For each of the four commands, record in the table whether or not the command line executes successfully. Enter in the table PD for Permission Denied OK for success

Exercise 3 Ro w

Command line

cd top

touch top/file

mkdir top/dir

ls –l top

0

chmod 000 top

---

PD

PD

PD

PD

1

chmod 100 top

--x

OK

OK

PD

PD

2

chmod 200 top

-w-

PD

PD

PD

PD

3

chmod 300 top

-wx

OK

OK

OK

PD

4

chmod 400 top

r--

PD

PD

PD

PD

5

chmod 500 top

r-x

OK

OK

PD

OK

6

chmod 600 top

rw-

PD

PD

PD

PD

7

chmod 700 top

rwx

OK

OK

OK

OK

Pointer operations

Valid pointer operations • assignment to a pointer of the same type • assigning a pointer to pointer of type (void *) and back (not in all systems) • adding or subtracting an integer (including increment and decrement) • subtracting or comparing two pointers which point to members of the same array • assigning or comparing to zero

Invalid pointer operations • adding two pointers • multiply, divide, shift, mask pointers • add float or double numbers to a pointer • assign pointers of different types without a cast

Which operations are illegal? int x, y; int *px, *py, *p; float *pf; px = &x; /** legal assignment **/ py = &y; /** legal assignment **/ p = px + py; /** addition is illegal **/ p = px * py; /** multiplication is illegal **/ p = px + 10.0; /** addition of float is illegal **/ pf = px; /** assignment of different types is illegal **/

Functions

Function to swap 2 values void swap_V(float x, float y){ float tmp = x; x = y; y = tmp; }

What is printed? main() { float x=5.0, y=6.0; printf("x = %6.2f, y = %6.2f\n", x, y); swap_V(x, y); printf("x = %6.2f, y = %6.2f\n", x, y); }

How to fix the bug?

Pass by value void swap_A(float *x, float *y) /** passes addresses of x and y **/ { float tmp = *x; *x = *y; *y = tmp; }

Bug fixed main() { float x=5.0, y=6.0;

printf("x = %6.2f, y = %6.2f\n", x, y); swap_A(&x, &y); printf("x = %6.2f, y = %6.2f\n", x, y); }

Arrays

Array is like a pointer • The array and pointer are closely related, in that x[i] and *(x+i) both get the (i+1)th element in the array, and &x[i] and (x+i) are both pointers to the (i+1)th element

Array is not exactly a pointer • An array name is a constant, while a pointer is a variable: int x[10], *px; px = x; px++; /** valid **/ x = px; x++; /** invalid, cannot assign a new value **/

• Also, defining the pointer only allocates memory space for the address, not for any array elements, and the pointer does not point to anything. • Defining an array (x[10]) gives a pointer to a specific place in memory and allocates enough space to hold the array elements.

Array as a return value • You can't say:

• But returning a pointer to malloc'd memory is fine:

int *f() {

int *f() {

int a[10];

int *a;

...

if ((a = malloc(10 * sizeof(int))) == NULL)

return(a);

...

} • because that 'a' array is deallocated as the function returns.

... return(a); } • because the malloc'd memory persists until free() is called on the pointer -- its existence is not tied to the duration of the execution of the function.

Function 1 to compute max value of an array int maximum_val1(int A[], int n) { int max, i; for (i=0, max=0; i max) max = A[i]; return max; }

Calling maximum_val1 main() { int i,n; int A[100], *max; printf("number of elements: "); scanf("%d",&n); printf("input %d values:\n", n); for (i=0; i0 ; n--, a++) if (*a > max) max = *a; return max; }

Calling maximum_val2 printf("maximum value = %d\n", maximum_val2(A,n));

Will max be printed? Is there a problem?

Function 3 to compute max value of an array int * maximum_ptr1(int *a, int n) { int *max = a; for ( ; n>0; n--, a++) if (*a > *max) max = a; return max; }

Calling maximum_ptr1 Int *max = maximum_ptr1(A,n); printf ("maximum value = %d\n", *max); Will max be printed? Is there a problem?

Function 4 to compute max value of an array int * maximum_ptr2 (int *a, int n) { int max = *a; for ( ; n>0; n--, a++) if (*a > max) max = *a; return &max; }

Calling maximum_ptr2 Int *max = maximum_ptr2(A,n); printf ("maximum value = %d\n", *max); Will max be printed? Is there a problem?

Array as function parameter a. How does the following function in C find the size of the array parameter? void f(int *p) { ... } b. Under what circumstances would the answer to part (a) be different if the parameter were of type "char *"?

Strings

String literals int main() { char a[20]; strcpy(a, "Hello"); printf("%s, world\n", a); return(0); }

How does it know to print just the first five chars of a[]?

Termination int main() { int i; char a[10]; for (i = 0; i < 5; i++) a[i] = 'a' + i; a[5] = 0; printf("%s\n", a); return(0); } '\0' and 0 are EXACTLY the same (both type int, even). But they mean something different to the human reading your code.

strcat int main() { char a[20]; strcpy(a, "Hello"); strcat(a, ", world"); printf("%s\n", a); return(0); }

What happens if that 20-byte array isn't big enough?

Comparing strings strcmp("hello", "hello") -> 0 strcmp("goodbye", "hello") -> -1 strcmp("hello", "goodbye") -> 1

if (strcmp(s, "foo") == 0)

Creating formatted strings with sprintf printf("%s\n", helloize("world"));

Why buf MUST be static?

char *helloize (char *s) { static char buf[100]; sprintf(buf, "Hello, %s", s); return(buf); }

What is the bug in this function?

Bug fix: Add limit on string: "Hello, %.90s"

Char * vs. char []

pmessage: amessage:

now is the time\0 now is the time\0

• There is an important difference between these definitions: char amessage[] = "now is the time"; /* an array */ char *pmessage = "now is the time"; /* a pointer */ • amessage is an array, just big enough to hold the sequence of characters and ’\0’ that initializes it. Individual characters within the array may be changed but amessage will always refer to the same storage. • On the other hand, pmessage is a pointer, initialized to point to a string constant; the pointer may subsequently be modified to point elsewhere, but the result is undefined if you try to modify the string contents.

Memory diagram

Memory diagram Computer memory: 1. Text segment 2. Initialized data segment 3. Uninitialized data segment 4. Stack 5. Heap OS AREA

============ System stack (local variables and function calls) ============ unused ============ System heap (data allocated here... alloc or alloc_array) ============ .text (read only) (program instructions sit in memory) ============ OS AREA

Process memory

Where the variables are stored • global variables -------> data • static variables -------> data • constant data types -----> code and/or data. Consider string literals for a situation when a constant itself would be stored in the data segment, and references to it would be embedded in the code • local variables (declared and defined in functions) --------> stack • variables declared and defined in main function -----> stack • pointers(ex: char *arr, int *arr) -------> heap or stack, depending on the context. C lets you declare a global or a static pointer, in which case the pointer itself would end up in the data segment. • dynamically allocated space(using malloc, calloc, realloc) --------> heap

Example 1 int x = 10;

void func() { int a = 0; int *p = &a: //Now it’s pointing to the memory on stack int *p2 = &x; //Now it’s pointing to the memory of data segment (global) chat *name = "ashok" //Now it’s pointing to the constant string literal stored in text char *name2 = malloc(10); //Now it’s pointing to the memory on the heap }

• dynamically allocated space (using malloc,calloc) --------> heap • Note : Pointer variables can point to the memory of any segment.

Example 2: 1 void Heap1() { int* intPtr; // Allocates local pointer local variable (but not its pointee)

Stack intPtr

X X X

Heap

Example 2: 2 // Allocates heap block and stores its pointer in local variable. // Dereferences the pointer to set the pointee to 42. intPtr = malloc(sizeof(int)); *intPtr = 42;

Stack intPtr

Heap

42

Example 2: 3 // Deallocates heap block making the pointer bad. // The programmer must remember not to use the pointer // after the pointee has been deallocated (this is // why the pointer is shown in gray). free(intPtr); Stack intPtr

Heap

Reading from files • fgets: reading lines of text, not portable • fscanf: reading formatted input string • fread: reading binary files, reading structs

• fgetc: reading a single character

fgets – end of line while( fgets (line, MAX_LINE, input_FP)!=NULL ) { line[strcspn(line, "\r\n")] = '\0'; insert (dictionary, line); } • fgets reads the line of text INCLUDING end-of-line characters • These characters are system-dependent • If you want to add the string from this line to an array of strings, for example, you need to strip end-of-line characters

• Always use the above code to get read of unwanted endings in string returned by fgets • It finds the first occurrence of such a character, and replaces it wit ‘\0’, effectively terminating the string