If you want to set the value of the pointer and pass it back, then b() needs to return a pointer:
int a(void) {
char *p = NULL;
p = b(p);
printf("%s", p);
return 0;
}
char * b(char * ptr) {
ptr = "test string";
return ptr;
}
Here p is initialized to NULL to avoid undefined behavior from passing an uninitialized value to a function. You could also initialize p to another string literal. Inside b, ptr is a copy of the pointer p that was passed to b. When you reassign the value stored in ptr to the address of the string literal "test string", the original pointer p is unchanged. By passing ptr back to the calling function, and reassigning p to the return value of b, the calling function can use the updated value.
As @M.M points out in the comments, this is somewhat redundant. The function b() could instead declare a pointer of its own, initialized to a string literal:
int a(void) {
char *p = b();
printf("%s", p);
return 0;
}
char * b(void) {
char *ptr = "test string";
return ptr;
}