Announcements Pointers about pointers Midterm next week Material covered up until June 18 (last week, signals) Allowed to have 1 cheat sheet No tutorial Come to class at 6 Test is 6:10 7:00 Assignment 3 is on the web site pointer is a memory address is 4 bytes like an unsigned int can address up to 2^32 bytes = 4 gb declaring a pointer allocates 4 bytes for the pointer itself - that's it! char * ptr; gets(ptr); will compile, but crash Pointer type pointers are typed char * pchar; vs. int * pint, struct mystruct * pstruct; similarity: all are 4 bytes difference: pchar++; //increment by 1 byte pint++; //increment by 4 bytes pstruct++; //increment by sizeof(mystruct) Example char * pchar; int * pint; struct mystruct * pstruct; int arry[3][3] = { {1,2,3, {4,5,6, {7,8,9; pchar = (char *)arry; pint=(int *)arry; pstruct=(struct mystruct *)arry; printf("pchar=%p,pint=%p,pstruct=%p\n",pchar,pint,pstruct); pchar+=1; pint+=1; pstruct+=1; printf("pchar=%p,pint=%p,pstruct=%p\n",pchar,pint,pstruct); pchar=0012ff50,pint=0012ff50,pstruct=0012ff50 pchar=0012ff51,pint=0012ff54,pstruct=0012ff58
Arrays Multi-dimensional arrays The name of an array is the memory address of the array It is a const pointer - cannot do arithmetic on it: char buf[100]; buf=buf+1; //illegal! but you can do this: char * ptr; ptr=buf; ptr+= 50; //points half way through this array. int twodbuf [3][3]; allocates contiguous chunk of memory that is 3x3xsizeof(int)=36 bytes twodbuf is of type int ** In other words: twdbuf is an array of int * so twodbuf[i] is of type int * (twodbuf[i])[j] is of type int Example: Truth about multi-dimensional arrays arry == 0x12ff50 0012FF50 01 00 00 00 02 00 00 00 03 00 00 00... 0012FF5C 04 00 00 00 05 00 00 00 06 00 00 00... 0012FF68 07 00 00 00 08 00 00 00 09 00 00 00... 0012FF74 CC CC CC CC 50 FF 12 00 50 FF 12 00 ÌÌÌÌPÿ..Pÿ.. No such thing. A 2d array is just made up of a bunch of 1D arrays There are only really ever 1D arrays
Dynamic allocation (2D arrays) parameter passing allocate 2d array of dimension: row,col of chars easyway (works only is col is same for every row): char * ptr=new char [row*col];//or ptr=(char*)malloc(row*col*sizeof(char)); //index the i,j element: char * pij = ptr + i*col + j; //harder, more general way. works even if col is not the same for every row. char ** ptr; ptr = new char * [row]; // or ptr=(char **)malloc(row*sizeof(char *)); for(i=0;i<row;i++) { ptr[i]= new char[col]; // or ptr[i]= (char )malloc(col*sizeof(char)); //index i,j element: ptr[i][j] When you pass an array, you really just pass the pointer: void myfunc(char * arry) is the same as void myfunc(char arry[]) pointers are passed by value! Consider: void assignptr(char * dst,char * src) { dst=src; char arry[10],* ptr; assignptr(ptr,arry);//does nothing. //how to fix?? Make files Dependency tracking Why Makefiles? Up until now, we've built projects using shell scripts Complete rebuild everytime something changes Ok for small projects But for large projects (10s of thousands of files, millions of lines of code) a rebuild is NOT ok Build ONLY what's needed (incremental build) Need to know: if I change a file, which other file(s) need to be updated too? Example: change a #define in a.h file, then all files that include the.h need to be recompiled Makefiles and the make utility allows you to do this
make General format of a makefile The make utility updates a bunch of files according to dependency stored in a "makefile" make -f mymakefile or just make If no file specified, automatically look for "makefile" The general format is: target: depdency1,dependency2... <tab> <how to create target based on dependencies> dependency1: <more dependencies> <tab><how to create dependency1>... Example Dependency tree of the above main1: main1.o reverse.o g++ -omain1 main1.o reverse.o main1.o: main1.cpp reverse.h g++ -c main1.cpp reverse.o: reverse.cpp reverse.h g++ -c reverse.cpp
touch clean make works by looking at time stamps if any dependencies have a newer time stamp, the target needs to be rebuilt Traces dependencies up the tree If after a full build you say "make", then nothing happends use touch to update the time stamp The above rules leave a lot of.o files around Usually, there is a "clean" rule: clean: rm -f *.o removes all the intermediate files but any build after this will be a full build Inter-Process Communication (IPC) Inter-process Communication Pipes (Haviland Ch. 7) Data exchange techniques between processes: message passing: files, s, sockets shared-memory model Limitations of files for IPC slow, others can modify files Limitations of s two processes must be related and running on the same machine. Limitations of sockets full network protocol stack required from PF_INET (necessary if the processes are on different machines) PF_LOCAL similar to s better performance than PF_INET between processes on the same machine
Producer/Consumer Problem Producer/Consumer Simple example: who wc l Both the writing process (who) and the reading process (wc) of a line execute concurrently. A is usually implemented as an internal OS buffer. It is a resource that is concurrently accessed by the reader and the writer, so it must be managed carefully. consumer should be blocked when buffer is empty producer should be blocked when buffer is full producer and consumer should run independently as far as buffer capacity and contents permit producer and consumer should never be updating the buffer at the same instant (otherwise data integrity cannot be guaranteed) producer/consumer is a harder problem if there are more than one consumer and/or more than one producer. File Descriptors Revisited () Used by low-level I/O open(), close(), read(), write() declared as an integer int fd; Not the same as a file stream : FILE *fp; A useful system call to convert a stream to a fd int fileno( FILE *fp); Of course it is possible to assign a stream interface to a file descriptor FILE *fdopen(int fd, const char *mode); Create with: int (int filedes[2]) filedes is a 2 integer array that holds the file descriptors for each end of the filedes[0] is open for reading filedes[1] is open for writing The is just an internal OS buffer Data treated in FIFO (first in, first out) basis lseek() will not work on s
Simple example:...other declarations... int p[2]; if((p)==-1) {...print error, quit... write(p[1],msg1,msgsize); write(p[1],msg2,msgsize); //read it back for(j=0;j<2;j++) { read(p[0],inbuf,msgsize); printf("%s\n",inbuf);...rest of program int (int filedes[2]) half-duplex (one-way) communication read user process fd[0] kernel fd[1] write Pipes only useful with fork() Recall file descriptors remain open across a fork() call int p[2]; if((p)==-1) {...print error, quit... if(!fork()) { write(p[1],msg1,msgsize); write(p[1],msg2,msgsize); else { //read it back for(j=0;j<2;j++) { read(p[0],inbuf,msgsize); printf("%s\n",inbuf);...rest of program What happens after fork? user process user process fd[0] fd[1] fd[0] fd[1] kernel
Direction of data flow? Pipes and File Descriptors fd[0] parent child to parent close fd[1] in parent and fd[0] in child) fd[1] kernel fd[0] child fd[1] parent to child (close fd[0] in parent and fd[1] in child) A forked child inherits file descriptors from its parent () creates an internal system buffer and two file descriptors, one for reading and one for writing. After the call, the parent and child should close the file descriptors for the opposite direction. Leaving them open does not permit full-duplex communication. Correct code dup2() int p[2]; if((p)==-1) {...print error, quit... if(!fork()) { close(p[0]); write(p[1],msg1,msgsize); write(p[1],msg2,msgsize); else { close(p[1]); //read it back for(j=0;j<2;j++) { read(p[0],inbuf,msgsize); printf("%s\n",inbuf);...rest of program Often we want the stdout of one process to be connected to the stdin of another process. Point one FD to the location pointed to by the other FD returncode = dup2( oldfd, newfd ); newfd and oldfd now refer to the same file if newfd is open, it is first automatically closed Note that dup2() refer to fds not streams
dup2() Process newfd oldfd oldfd = open( file ); dup2(oldfd, newfd); file ()/dup2() example /* equivalent to sort < file1 uniq */ int fd[2]; int filedes = open( file1, O_RDONLY); dup2(filedes, fileno(stdin)); (fd); if(fork() == 0) { dup2(fd[1], fileno(stdout)); close(fd[0]); close(fd[1]); execl( /usr/bin/sort, sort, (char *) 0); else { dup2(fd[0], fileno(stdin)); close(fd[1]); close(fd[0]); execl( /usr/bin/uniq, uniq, (char *) 0); Direction of data flow? Reading and writing to a dup2 filedes stdin stdout dup2 open parent file1 uniq fd[0] fd[1] fork filedes stdin stdout kernel child dup2 sort fd[0] fd[1] close A read on an empty will block until there is something to read. A write on a full will block until there is more space. (Pipes have a finite size.) Writing to a that has been closed by the other end will result in a SIGPIPE or Broken Pipe message. Read will return 0 if the write end of the is closed.
popen() and pclose() popen() popen() simplifies the sequence of: generating a forking a child process duplicating file descriptors passing command execution via an exec() Usage: FILE *popen( const char *command, const char *type ); Example: FILE *FP; FP = popen( /usr/bin/ls *.c, r ); r Command w Us Us Command