START OF WEEK 5 of C <<< FILE TIMESTAMP >>> -The up to date file is in: /usr/courses/cps393/dwoit/courseNotes/ -If you are viewing your own copy, check it is up to date -If you are viewing from a link in the Course Outline, be aware it may be outdated. <<< Function Pointers >>> we can declare a pointer to a function the pointer points to code, not data (that code is normally the start of the executable code of a function) we don't allocate memory for a function pointer because what it points to already exists (function code) //source: funcPtr.c #include #include //a basic function f int f (int a, int b) { printf("a=%d and b=%d\n",a,b); return (a+b); } int main (void) { //as seen before... //xPtr is pointer to a VARIABLE x int x=2; int (*xPtr); //parens unnecessary, but OK xPtr=&x; printf("x=%d\n",*xPtr); //prints 2 //fPtr is a pointer to a FUNCTION. That function to which //it points has 2 int parameters and returns an int int (*fPtr)(int,int); //function f has right type (2 int params and returns int) //so make fPtr point to f fPtr=&f; //Call f using fPtr x=(*fPtr)(5,8); printf("x=%d\n",x); exit(0); } A function's name actually IS its address, so don't even neeed the '&' in above program. e.g., this line: fPtr=&f; could be this: fPtr=f; this line: (*fPtr)(5,8); could be this: fPtr (5,8); code in funcPtrNN.c does this <> Just like any pointer, can have an array of them, e.g.: //source: funcPtrArray.c #include #include int add(int a, int b) { return (a+b); } int sub(int a, int b) { return (a-b); } int mul(int a, int b) { return (a*b); } int main (void) { //fpA is array of function pointers. (note the [3]) int (*fpA[3])(int,int); fpA[0]=add; fpA[1]=sub; fpA[2]=mul; //or could declare/initialize with {} like any array //int (*fpA[])(int,int) = {add,sub,mul}; printf("2+5=%d\n", (*fpA[0])(2,5)); printf("2-5=%d\n", (*fpA[1])(2,5)); printf("2*5=%d\n", (*fpA[2])(2,5)); //or this, if you don't like to see the explicit pointer // printf("2+5=%d\n", fpA[0](2,5)); // printf("2-5=%d\n", fpA[1](2,5)); // printf("2*5=%d\n", fpA[2](2,5)); exit(0); } int main (void) { //fpA is array of function pointers int (*fpA[])(int,int) = {add,sub,mul}; printf("2+5=%d\n", (*fpA[0])(2,5)); printf("2-5=%d\n", (*fpA[1])(2,5)); printf("2*5=%d\n", (*fpA[2])(2,5)); // or this, if you don't like to see the explicit pointer // printf("2+5=%d\n", fpA[0](2,5)); // printf("2-5=%d\n", fpA[1](2,5)); // printf("2*5=%d\n", fpA[2](2,5)); exit(0); } <> Just like any pointer, it can be passed as an argument //source: funcPtrArg1.c //see funcPtrArg1I.c for version where sum,prod return ints #include #include //basic function prints sum of its arguments void sum (int a, int b) { printf("%d",a+b); } //basic function prints product of its arguments void prod (int a, int b) { printf("%d",a*b); } //a function that takes a basic function as a parameter //and calls that basic function void apply(void (*f)(int,int), int x, int y) { (*f)(x,y); } int main (void) { apply(sum,2,4); printf("\n"); apply(prod, 2,4); printf("\n"); exit(0); } Our gcc allows us to omit the argument types and *, so the program above could be written more simply. See funcPtrArg1S.c <> Just like any pointer, it can be returned from a function //source: funcPtrArg2.c #include #include int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } //getFunc takes one char argument. //getFunc returns a pointer to a function (that returns //an int and takes 2 int arguments) int (*getFunc(char op))(int,int) { //or //typedef int (*FPTR)(int, int); //FPTR getFunc(char op) { if (op == '+') return &add; else if (op == '-') return ⊂ else if (op == '*') return &mul; else return NULL; } int main(void) { int (*func)(int, int); //or FPTR func; int a = 3; int b = 5; int r; char c; c='+'; func = getFunc(c); r = (*func)(a, b); //or r = func(a,b); printf("%d %c %d = %d\n", a, c, b, r); c='*'; func = getFunc(c); r = (*func)(a, b); printf("%d %c %d = %d\n", a, c, b, r); return 0; } -funcPtrArg2Loop.c does above program using a loop and user input <<< External Storage Specifier >>> -extern can be used for functions or variables to extend their visibility in the program Aside: cannot be used in a struct declaration, but could be used for a variable whose type is struct xyz if needed -recall: -a variable/function DECLARATION says variable/function exists somewhere in this program, but no storage/memory is allocated -a variable/function DEFINITION does same as declaration PLUS allocates memory/storage -a variable/function may be declared multiple times in program, but can be defined only once << extern in Functions >> -Function declaration is its prototype, and definition is its code -Nothing special needed to make functions extern, since extern already implicit in function declarations -So when compiler sees this: int x (int arg1, char arg2); it treats it as if it were this: extern int x (int arg1, char arg2); Thus, function x is visible to whole program -so it can be called from ANYWHERE in ANY file of the program (as long as the file with the call also has a declaration of function x.) -extern not necessary for functions, but often used anyway, to remind user that function is defined elsewhere, e.g., see printf in /usr/include/stdio.h << extern Variables >> -extern NOT implicit for variables -note that when code has this, x is both declared and defined: int x; -to declare x WITHOUT defining it (without allocating storage): extern int x; -this is a reference to a variable of the same name DEFINED in another file. So x is just a name--it has no storage. Can compile this file with gcc -c. But if want to run it, compile along with another file that defines x. -note: if you had program in a single file, extern unnecessary, as could extend x's visibility to the whole program by simply making x global. But if need x in multiple files, need extern. ext1.c compiles and runs fine; x is defined (and declared) as global to whole program (remember globals are automatically initialized to 0): //source: ext1.c //global x #include int x; int main(void) { printf("x=%d\n",x); return 0; } ext2.c compiles and runs; x is declared (not defined) so has no storage and can't be used //source: ext2.c #include extern int x; int main(void) { //printf("x=%d\n",x); return 0; } ext3.c won't compile since x has no storage allocated and variable x does not exist in the program (could compile with -c but no executable) //source: ext3.c //this won't compile #include extern int x; int main(void) { x=5; printf("x=%d\n",x); return 0; } ext4.c is fine since ext.h defines x //source: ext4.c #include #include "ext.h" extern int x; int main(void) { x=5; printf("x=%d\n",x); return 0; } //source: ext.h int x; ext5.c compiles with a warning, and runs extern only declares x, but adding an initializer at that point also allocates storage. However, compiler warning, since extern really only declares //source: ext5.c #include extern int x=2; int main(void) { x=5; printf("x=%d\n",x); return 0; } << extern to Get Global Variables in Multiple Files >> -typical method is to use extern in a header file -.h file included by all files needing global variable -one (and only one) of those files also must define the variable (initialize it) -e.g., global variable x is shared among globMain.c, glob[12].c glob1.c gets storage for x (and initializes it) make -f globMakefile to make executable glob //source: globMain.c #include "globVars.h" #include "globFuncs.h" #include int main(void) { showx(); x = x+20; printf("did x+20\n"); showx(); addone(); printf("did addone\n"); showx(); return 0; } //source globVars.h extern int x; //declaration //source: globFuncs.h void addone(void) ; void showx(void) ; //source: glob1.c #include "globVars.h" //variable declaration int x = 5; //variable definition (must match declaration) void addone(void) { x=x+1; } //source: glob2.c #include "globVars.h" //variable declaration #include void showx(void) { printf("global var x=%d\n",x); } Can we make a variable global in ONE file only, and hidden to all other files? e.g., x global to functions in F.c but hidden from main in M.c? YES. make it static in that file. Note: can't just make x global in F.c because M.c could hijack x by doing: extern int x; then it could access F's global x. Can we make a variable global in 2 files, but still hidden to all other files? e.g., x global to functions in F.c and G.c, but hidden from main in M.c? NO. Why not? To make x global to [FG].c, need to have extern int x in both [FG].c and have one of them define x. (or extern in one and definition in other). But because x is extern, main in M.c can hijack it as above. -See example of this below: -M.c below grabs [FG].c's global x using either of these: extern int x; inside main int x; before main -Note that if M.c instead did: int x; inside main then x would be different variable (only local to main) -with "extern int x" or first "int x" uncommented this prints: A says x=1 B says x=19 //M changed [FG]'s x main says x=19 -with second "int x" (inside main) uncommented this prints: A says x=1 B says x=2 //M changed only its local x main says x=19 //source: F.c //compile with G.c M.c #include int x; void A(void) { x=1; printf("A says x=%d\n",x); x=2; } //source: G.c //compile with M.c F.c #include extern int x; void B(void) { printf("B says x=%d\n",x); } //source: M.c //compile with [FG].c #include //int x; //[FG].c's global x void A(void); void B(void); int main (void) { extern int x; //[FG].c's global x //int x; //local (to main) x A(); x=19; B(); printf("main says x=%d\n",x); } If x were declared static in F.c, then M.c can't access it. (won't compile). But neither could G.c access it. <<< Variadic (or Vararg) Functions >>> -function takes a variable number of arguments, like printf, scanf -#include -in program below, variadic function sumargs: -takes variable number of arguments -first arg is a count of how many args remain -returns sum of all but first arg - sumargs(2, 5, 3) returns 8 - sumargs(4, 2, 1, 2, 10) returns 15 -these are given (see Variadic Arguments below): va_list: argument list type va_arg: returns next argument on argument list va_start: initialize argument list va_end: stop traversing argument list and cleanup -ap is typical variable name for the va_list -every va_start must have matching va_end in same function. Required. -can have multiple traversals of arg list, each enclosed in its own va_start and va_end //source: var1.c #include #include int sumargs (int count,...) { va_list ap; int i, total; va_start(ap, count); //initialize argument list //which starts after argument named count total = 0; for (i = 0; i < count; i++) total = total + va_arg(ap, int); //get next argument va_end(ap); //cleanup for ap. Required. return total; } int main (void) { printf ("%d\n", sumargs (4, 2, 1, 2, 10)); //15 printf ("%d\n", sumargs (10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); //55 printf ("%d\n", sumargs (0)); //0 return 0; } << Variadic Arguments >> -prototype must have one or more fixed arguments, specified in the usual way, followed by optional arguments, specified by, literally: ... -the fixed arguments each have a type, and access them as usual -can access the optional arguments one-by-one, in order given How: -use va_start to initilize an argument pointer variable of type va_list (variable typically named ap) -va_start's second argument must be name of last fixed argument in function's argument list e.g., if had this: int F (int x, int y, ...) then need this: va_start(ap,y) -use va_arg to get next optional argument -can stop any time (don't need to iterate through whole list) but if go too far (past list end) get junk -use va_end to indicate no longer need the argument pointer variable you initialized with va_start (system will clean up if you forget). -may pass ap to a function, and loop through it in that function instead of function that did va_start -can initialize multiple argument pointer variables if want to: -initialize each with own va_start (and kill it with own va_end) -each pointer still iterates through the same argument list, but at its own pace << Number and Types of Arguments >> -a Variadic function can't figure out the number and type of arguments it was called with. So, -function DESIGNER must provide some kind of RULE for user to follow to help function figure it out. -typical kinds of rules: -one fixed argument which specifies the count of the optional arguments (as in var1.c above) -a sentinal as last optional argument. e.g., if the optional args are pointers, the last argument could be NULL to indicate end of argument list -one fixed argument can contain a pattern indicating the number and types of the optional arguments. e.g., the format specifier string of printf does this. Promotions: -variadic function's optional arguments are subject to DEFAULT ARGUMENT PROMOTIONS. -char and short int promoted to int -unsigned short int promoted to unsigned int -float is promoted to double -e.g., if arg type char is passed, variadic function accesses it with va_arg (ap, int) var2.c shows -the float arguments passed to sumargs get promoted to double -use of a string as required argument to help determine number and type of optional arguments //source: var2.c #include #include #include double sumargs (char *str,...) { va_list ap; double tot=0; int k; //number of optional arguments int T; //type: 0 for int 1 for double va_start(ap, str); //initialize argument list if ((strcmp(str,"2ints"))==0) {T=0; k=2;} else if ((strcmp(str,"3ints"))==0) {T=0; k=3;} else if ((strcmp(str,"2doubles"))==0) {T=1; k=2;} else if ((strcmp(str,"3doubles"))==0) {T=1; k=3;} if (T==0) //type int for (int i=0; i