-1

I have char* that I have defined in the main function. To my understanding, when passing around a pointer and assigning it a value anywhere, the value that the pointer points to is changed universally. But how come it doesn't behave like this here:

#include <stdio.h>

void idk(char *str) {
  printf("1.idk: %s\n", str);
  str = "hello world";
  printf("2.idk: %s\n", str);
}

int main() {
  char *str = "init value";
  printf("1.main: %s\n", str);
  idk(str);
  printf("2.main: %s\n", str);
  
  return 0;
}
  

Output:

1.main: init value
1.idk: init value
2.idk: hello world
2.main: init value // why is this not "hello world"?
darkstar
  • 829
  • 11
  • 23
  • 3
    This is a dupe but I don't have the time to find it. Str is not initialized, thus accessing it is UB. And a pointer to a pointer should be passed differently – JHBonarius Aug 10 '22 at 06:03
  • Or [Changing address contained by pointer using function](https://stackoverflow.com/questions/13431108/changing-address-contained-by-pointer-using-function) – Gerhard Aug 10 '22 at 09:54

5 Answers5

3

Because you changed the pointer, not the value it points to.

Consider this code.

void idk(int num) {
  num = 42;
}

You wouldn't expect this to change the value outside the function. num is a copy of the value. Same thing with a pointer. A pointer is just a number. You changed the number, not the value it points at.

If you want to change the number, you pass in a pointer to it and dereference it to access its value.

void idk(int *num) {
  *num = 42;
}

Same thing, we need a pointer to the char *. A char **, a pointer to a pointer, a "double pointer".

First, make sure str is initialized, using an uninitialized value is undefined behavior. We'll use strdup to make sure the value is not constant (not strictly necessary, but it avoids having to deal with constant pointers vs pointers to constants).

Then we pass in a pointer to the char *, a char **.

Inside the function, we dereference the char ** to change what it points at.

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

void idk(char **str) {
  printf("1.idk: %s\n", *str);
  *str = "hello world";
  printf("2.idk: %s\n", *str);
}

int main() {
  // Make sure str is initialized and not constant.
  char *str = strdup("testing 123");

  printf("1.main: %s\n", str);

  // Pass in a pointer to the char *
  idk(&str);

  printf("2.main: %s\n", str);
  
  return 0;
}
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • So you basically got a pointer to ``char*``, dereferenced it and assigned the string it's value. Isn't that literally what I did but with extra steps? I don't understand how it makes a difference. Clearly it does I just don't know why. I hate C if you can't tell lol. – darkstar Aug 10 '22 at 06:48
  • @darkstar You might want to read my answer, too. – the busybee Aug 10 '22 at 07:09
  • @darkstar C is perhaps the hardest programming language that is not a joke language (or is it?), it throws a lot at you all at once. The important thing here is a pointer is a number and numbers are *copied* into functions. `str = "foo"` is the same as `num = 42`, you're assigning a number. To change the outside value you pass in a pointer and derefrence it. To change the value of an `int` you pass in an `int *`. You want to change the value of a `char *`, so you pass in a `char **`. This would all be different if you were doing `str[0] = 'f'`, that is an implicit pointer dereference. – Schwern Aug 10 '22 at 18:11
1
  1. In your function idk, your are not changing the pointed value but the pointer itself. That's mean your are changing a local copy of your pointer. For this to work you need to pass a pointer of pointer
void idk(char **str) {
  printf("1.idk: %s\n", *str);
  *str = "hello world";
  printf("2.idk: %s\n", *str);
}
int main() {
  char *str;
  printf("1.main: %s\n", *str);
  idk(&str);
  printf("2.main: %s\n", *str);
  return 0;
}

In this code your are changing the pointed value of your pointer of pointer so, your changing the value of your pointer. And then str will point to Hello World.

  1. As mention in comments, your pointer are not initialized, accessing it's pointed value like you do in printf is undefined behavior. To avoid that you should always initialized all your variables. For a pointer you can initialize with NULL.

  2. Be cautious when you do things like

char* str = "hello world";

because the "hello world" is a string literal and so it is a const string and thus can not be modified. Prefer to do this

char const* str = "hello world";

This will prevent you to modified your string content but you can still modified the pointer it self.

Stiven
  • 309
  • 1
  • 9
1

Perhaps this helps make clear what's happening...

int main() {
    char *str = "init value";

    printf( "1.main: %s\n", str );

    /* idk( str ) */ /* Function call pushes copy of ptr onto stack */
    {
        char *strInFunc = str; // function takes local copy off stack
        printf( "1.idk: %s\n", strInFunc );
        strInFunc = "hello world";
        printf( "2.idk: %s\n", strInFunc );
    }
    /* At this point, 'strInFunc' no longer exists */
    printf("2.main: %s\n", str);
    return 0;
}
Fe2O3
  • 6,077
  • 2
  • 4
  • 20
1

Look at parameters as local variables, which are initialized with the arguments of the call. Actually, compilers generate such code, so this "think model" is realistic.

Therefore, the parameter str of your function idk() uses a separate space, not the space of the variable str in main(). Their scopes are even separate, the names do not "collide" or "shadow" or "connect" each other. Both functions do not even know the names after compiling.

Where you call idk(), the value of main's variable str is copied into idk()'s parameter str.

When you changed this value inside idk(), the variable in main() is not touched.

This is called "passing by value", and is true even for pointers. In this sense, C only uses passing by value. I think this is a very clear concept.

"Passing by reference" is achieved, if you access the value pointed to by such a pointer. For example, you can change the string in main(), if you dereference the pointer:

void idk(char *str) {
  printf("1.idk: %s\n", str);
  str[0] = 'e'; /* str[0] is the same as *(str + 0) */
  str[1] = 'x'; /* str[1] is the same as *(str + 1) */
  printf("2.idk: %s\n", str);
}

Just be aware of the size of objects pointed to, and do not access memory outside the object.

the busybee
  • 10,755
  • 3
  • 13
  • 30
1

Yes it is true that passing pointers is a way to have functions edit the variables passed to them, but not in the way that you're trying to do it.

When you pass an argument to a function in C (pointer or otherwise), a new variable is created for your function to do what it pleases with, and the value of the argument is put in this new variable. This new "local" variable will be discarded when your function returns.

Where pointers get around this is you can dereference them inside of your functions to edit the value(s) they point to, so the changes will be reflected outside of the function.

Consider the following:

#include <stdio.h>

void bar(int local_foo) {
  local_foo = 21;
}

void baz(int *local_foo_ptr) {
  *local_foo_ptr = 21;
}

int main() {

  int foo = 19;
  printf("First: %d\n", foo);

  bar(foo);
  printf("Second: %d\n", foo);

  baz(&foo);
  printf("Third: %d\n", foo);

}

A small clarification: you may not have encountered the & operator before. What it does is gets a pointer the the variable it is applied to. So in

baz(&foo);

what is happening is baz() is being called, and a pointer to foo is being passed to it.

Anyways, bar() fails to make any change to foo because all it does is store 21 into local_foo. This does nothing to change foo in main().

However, baz() is passed a pointer to foo (which is stored in local_foo_ptr), and it dereferences this pointer to change the value it points to, which in this case is foo in main(). Thus the output of this program is:

First: 19
Second: 19
Third: 21

Now consider your program again, but with the variable names changed to make things easier to understand:

#include <stdio.h>

void idk(char *local_str) {
  printf("1.idk: %s\n", local_str);
  local_str = "hello world";
  printf("2.idk: %s\n", local_str);
}

int main() {
  char *str = "init value";
  printf("1.main: %s\n", str);
  idk(str);
  printf("2.main: %s\n", str);
  
  return 0;
}

In idk(), only local_str is changed, and str in main() is unaffected. Thus after the call to idk(), str still points to the string "init value", and this is reflected in the output.

One way to get around this is to pass a pointer to str to idk(), and dereference that to change str in main(). This is usually called using a "double pointer". Here's one way this could be done:

void idk(char **local_str_ptr) {
  ...
  *local_str_ptr = "hello world";
  ...
}

int main() {
  ...
  idk(&str);
  ...
}

In this, we generate a pointer to str and pass that as an argument, thus the function can dereference this pointer and change the value of str in main().

That said, there's more than one way to skin a cat, and this is by no means how it "has to" or "should" be done.