Sunday, May 28, 2017

C Programming tip: Pass struct pointer to a function as an argument

I'm taking a class in "operating systems" programming this spring, focused on Linux. So, this post may not be much use if you're working in Windows. In my previous assignment we used pthreads to practice multi-threading concepts. One question that came up for me was how to pass multiple arguments to a function called by pthread_create. The answer I kept finding was to pass a pointer to a struct. I had never done that before, wasn't really sure how it was supposed to work and just worked around it in other ways. But in working through my most recent assignment, I decided this was a technique I really wanted to use for the sake of flexibility, even though I wasn't using pthread_create.

The assignment was basically to write a command shell. I won't go into all the details in this post, but just want to stick to passing a struct pointer. The technique works well for pthread_create also.

Step 1: Create a struct with the multiple variables you want to pass as arguments
 25: struct params {  
 26:   int nums[4];  
 27:   char* chPtr;  
 28:   char** argt;  
 29:   char** argu;  
 30: };  

Step 2: Declare an instance of the struct and set some values for your struct member variables
 65: struct params genericParams;
 66: int o;
 67: for(o = 0; o < 4; o++) {
 68:    genericParams.nums[o] = 0;   //initialized to some known value
 69: }
 70: char startDir[512];
 71: memset(startDir, '\0', sizeof(startDir));   //ensure all indices hold the null terminator character
 72: getcwd(startDir, 512);
 73: genericParams.chPtr = startDir;
 74: char* argr[512];
 75: char* args[512];
 76: genericParams.argt = argr;
 77: genericParams.argu = args;

What's going on in this block? After declaring an instance of the struct and initializing the int array to all zeros, I set up a char to hold the path of the current working directory  and store that path in the char (lines 70 - 72). The reason for using memset to initialize all array indices to the null terminator character is to make sure no matter what we put into the array other functions that try to make use of the string will be able to recognize where it ends. If we skip this step we might wind up referencing uninitialized memory, which could hold anything at all.

Line 73 points the chPtr member at the startDir. In this way, we can later read and modify contents of startDir if we want without having to go through a lot of other steps to copy it properly into a struct member.

Lines 74 and 75 declare arrays of pointers to char. We can use each of these array location to hold a separate char*. Remember a char* is one way of representing a string in C.

Lines 76 and 77 point the char** members in our struct at argr and args, giving us a pair of pointers to arrays of char pointers. This provides a lot of flexibility when it comes to manipulating our strings later.

Step 3: Call your function
 163: retVal = childExec(&genericParams);  

By passing &genericParams in our function call, we're really passing the address of the struct instance.

Step 4: Receive the parameters and perform some processing
 433: int childExec(struct params *aStruct) {  
 434:     int o;
 435:     for(o = 0; o < 4; o++) {
 436:         printf("aStruct->nums[%d] = %d\n", o, aStruct->nums[o]);  
 437:     };
     //more code to make use of your other variable members ...
 489: }

Here we receive the address of the struct instance and store it in a pointer, aStruct. Line 436 then just prints the contents of each integer in the nums member array. Since aStruct is a pointer to struct, not itself a struct, we use the -> operator to reference member variables instead of a dot.

That's all there is to this. I hope someone finds it helpful. At least it will serve as a reminder to me on how the heck I did that thing. I will try to follow-up with tips on some of the other techniques I got to make use of in this project. So, do check back.