Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing char * vs char ** as parameters to a function in C

I've read several discussions of passing char * in C.

stackoverflow: passing-an-array-of-strings-as-parameter-to-a-function-in-c
stackoverflow: how-does-an-array-of-pointers-to-pointers-work
stackoverflow: whats-your-favorite-programmer-ignorance-pet-peeve
drexel.edu: Character arrays

Many of them include discussions of arrays, but I want to stay away from that.

I'm writing a sample program to teach myself about the passing of char * and char ** in C. This is an exercise in passing char *, without using (pointers to) arrays. Also no concerns for execution efficiency. :-)

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void get_args_works(int, char **, char **);
void get_args_broken(int, char **, char *);
char *get_string(int, char **);

int main(int argc, char **argv)
{
  char *string_works;
  char *string_broken;

  get_args_works(argc, argv, &string_works);
  get_args_broken(argc, argv, string_broken);

  printf("in main string_works (%p) = %s\n",string_works,string_works);
  free(string_works);

  printf("in main string_broken (%p) = %s\n",string_broken,string_broken);
  free(string_broken);
}

void get_args_works(int argc, char **argv, char **string)
{
    *string = get_string(argc, argv);
    printf("in get_args_works %p string %s\n",*string,*string);
}

void get_args_broken(int argc, char **argv, char *string)
{
  string = get_string(argc, argv);
  printf("in get_args_broken %p string %s\n",string,string);
}

char * get_string(int argc, char **argv)
{
  int i;
  char *string;
  string = malloc(40);

  // placeholder in case -s switch not found below
  strcpy(string,"-s switch not found below");

  for(i = 0; i < argc; i++)
    {
      if(argv[i][0] == '-')
        {
          switch(argv[i][1])
            {
            case 's':
              // release above malloc(40) for "-s switch not found below"
              free(string);
              // make room for storing variable
              string = malloc(strlen(argv[++i]) + 1);
              // the argv just after -s
              strcpy (string,argv[i]);
              break;
            }
        }
    }
  return string;
}

You can also view the same code on github

The above code is somewhat self documenting. main() declares two char * variables, and passes them as parameters to their respective get_args() functions.

Each get_args() function calls char * get_string(int, char **), using the exact same call (but different way to collect the return value).

get_string() works fine; it does a malloc() and returns the pointer back to the calling function. That code works, and each get_args() function receives the return value as I expect.

But then, when the get_args() functions return to main(), why does the dereferenced pointer value get back to main (from get_args_works(), but not the pointer's value (from get_args_broken())?

(i.e. I can see that if I dereference the pointer (&string_works) when sending as a parameter, it works. But why? Isn't char * string_broken already a pointer? Why does it need the "extra" dereference when sending as a parameter?)

I'm hoping for a winning answer that explains how you (yes, you) conceptualize sending char * as a parameter vs receiving it as the function's return value.

like image 838
Thunder Rabbit Avatar asked Nov 22 '10 06:11

Thunder Rabbit


2 Answers

int get_args_broken(int argc, char **argv, char *string)
{
  string = get_string(argc, argv);
  printf("in get_args_broken %p string %s\n",string,string);
}

You're only modifying the string local (automatic) variable. That's not visible to the caller in any way. Note that this means you're freeing a wild pointer in main.

It's wrong for the same reason:

int get_sum(int sum, int a, int b)
{
  sum = a + b;
}

is; the parameter is copied by value. Also, you're not returning an int (as you declared you would).

int get_args_works(int argc, char **argv, char **string)
{
    *string = get_string(argc, argv);
    printf("in get_args_works %p string %s\n",*string,*string);
}

is correct (except the missing return). You're not modifying string, which would be pointless. You're modifying the object at the location in string, which in this case is a char *.

EDIT: You would need to triple * the argv if there was a function calling main, and you wanted to set that function's variable to a different char **. E.G.

void trip_main(int *argc, char ***argv)
{
  *argc = 10; 
  *argv = malloc(*argc * sizeof(char *));
}

void caller()
{
  char **argv;
  int argc;
  trip_main(&argc, &argv);
}
like image 196
Matthew Flaschen Avatar answered Nov 06 '22 15:11

Matthew Flaschen


One of the needs to use Pointer to a pointer (here get_args_works()) is to modify (or return) more than on variable from a function, as in C it's not possible to return more than one variable.

get_args_works() works 'coz, you are passing pointer to a pointer & a reference to it is there in your main().

But in get_args_broken() you are passing just a pointer. Nothing wrong here, now you do malloc() & return back the memory allocated string to get_args_broken(), still nothing wrong here. But now, this mem allocated string is local & main() does not have a reference to this var. So when you dereference char *string_broken; in main() it might cause undefined behavior.

Hope this's clear.

like image 22
Srikar Appalaraju Avatar answered Nov 06 '22 15:11

Srikar Appalaraju