Why is the pointer passed into the function still NULL?

  • Thread starter Malcolm Nooning
  • Start date
M

Malcolm Nooning

Hello,
My goal here is to be able to understand why the code below results in
the printing of "maintable is NULL.". I saw a way to do what I wanted
using typedef string ..., but doing that does not help me to
understand what is going wrong here. Wy doesn't the variable
maintable change?
Thanks


#include <stdio.h>

void get_table (char**);

int main (void) {
int i, j;
char *maintable = NULL;
get_table((char**) maintable);

if (maintable == NULL) {
puts("maintable is NULL.");
} else {
puts("maintable is not NULL.");
for (i=0; i<2;i++) {
printf("%s\n", maintable);
}
}
}

void get_table (char ** table) {
char* functable[] = {"Okay", "Help"};
table = functable;
}

/* Prints "maintable is NULL." */
 
M

Malcolm Nooning

Same result if I (more properly) change * to ** in the line below.
char **maintable = NULL;
 
M

Malcolm Nooning

Thank you very much, especially for your thoroughness. Nothing helps
in understanding like a really good explanation.
 
J

John Bode

Hello,
My goal here is to be able to understand why the code below results in
the printing of "maintable is NULL.".  I saw a way to do what I wanted
using typedef string ..., but doing that does not help me to
understand what is going wrong here.  Wy doesn't the variable
maintable change?
Thanks

You have several problems. First of all, remember that in C all
function parameters are passed by value; the formal parameter is a
different object in memory from the actual parameter, so if the
function writes to the formal parameter, the actual parameter isn't
affected. If you want the function to be able to modify it's inputs,
you must pass a *pointer* to the thing you want to modify. For
example,

void foo (int x)
{
x = 5;
}

void bar(void)
{
int a = 3;
foo(a);
printf("a = %d\n", a);
}

The expression x in foo and the expression a in bar refer to
completely different objects; writing to x doesn't affect a, so the
output of bar is "a = 3". If we want foo to modify the contents of a,
we must pass a pointer to a, like so:

void foo (int *x)
{
*x = 5;
}

void bar(void)
{
int a;
foo(&a); // the expression &a evaluates to the *location* of
a.
printf("a = %d\n");
}

Now the expression *x in foo and the expression a in bar both refer to
the same object, so writing to *x affects a.

Note that I used the address-of operator & to get the address of a and
pass that to foo; I didn't just cast a to int *.

This same rule applies for any type parameter; if you want a function
to modify a pointer (that is, change where the pointer points to), you
must pass a pointer to that pointer:

void foo (int **x)
{
*x = &some_other_integer; // where some_other_integer has a
// lifetime outside of foo
}

void bar (void)
{
int *a = &some_integer;
foo(&a);
printf("*a = %d\n", *a);
}

Again, the expression *x in foo and the expression a in bar refer to
the same object, it's just that this time that object is a pointer to
an integer, rather than the integer itself. Writing to *x updates the
*pointer* value, not the integer value.

Your next problem is that your off by one level of indirection in your
types. The expression functable in get_table has type char *[2],
which will "decay" to char **, not char *. Thus, you want maintable
in main to have type char **, and the parameter table will need to
have type char ***.

Your final problem is that functable has auto extent; once the
get_table function exits, functable will no longer exist (the memory
that it occupied is released for something else to use, and the
pointer to it will no longer be valid). You can deal with this
several different ways. One is to declare functable as static; this
allocates the memory for it in such a way that it's not released after
the function exits:

static char *functable[] = {"Okay", "Help"};

Another option is to define the array at file scope, which has the
same effect as declaring it as static in the function, although the
functable identifier is visible to the entire program now:

char *functable[] = {"Okay", "Help"};
...
int main(void)
...
void get_table(char ***table)
{
*table = functable;
}

Or, you could declare the memory dynamically, which remains allocated
until you deallocate it explicitly:

void get_table(char ***table)
{
*table = malloc(sizeof **table * 2);
if (*table)
{
*table[0] = malloc(strlen("Okay") + 1);
if (*table[0])
strcpy(*table[0], "Okay");
*table[1] = malloc(strlen("Help") + 1);
if (*table[1])
strcpy(*table[1], "Help");
}
}

The downside of this method is that you'll have to free up that memory
at the end of the program:

free(maintable[0]);
free(maintable[1]);
free(maintable);
 
M

Malcolm Nooning

I am very close. The code pasted further below prints out
maintable is not NULL.
A01 A02 A03 A04
Segmentation fault (core dumped)
I know the problem has to do with pointer indirection, but I cannot
reason (or guess) it out. Where is the bug?

------- Paste of updated code snippet

#include <stdio.h>

void print_table (char ***);
void get_table (char ***);

int main (void) {
char ***maintable;
get_table(maintable);

if (maintable == NULL) {
puts("maintable is NULL.");
} else {
puts("maintable is not NULL.");
print_table(maintable);
}
}

void get_table (char ***table) {
static char *functable[2][4] =
{
"A01", "A02", "A03", "A04",
"B01", "B02", "B03", "B04"
};
*table = functable;
}

void print_table (char ***ptable) {
int i, j, k;
for (i=0; i < 2; i++) {
for (j=0; j < 4; j++) {
for (k=0; k < 3; k++) {
printf("%c", ptable[j][k]);
}
printf(" ");
}
printf("\n");
}
};
 
B

Ben Bacarisse

Malcolm Nooning said:
I am very close. The code pasted further below prints out
maintable is not NULL.
A01 A02 A03 A04
Segmentation fault (core dumped)
I know the problem has to do with pointer indirection, but I cannot
reason (or guess) it out. Where is the bug?

There is not really one bug more a general misunderstanding about how a
function can alter a variable in the calling function and another
misunderstanding about the distinction between arrays and pointers.
Specifically about pointers to pointers and 2D arrays. I think you'd
find Q6.18 of the C FAQ useful:

http://c-faq.com/aryptr/pass2dary.html
------- Paste of updated code snippet

#include <stdio.h>

void print_table (char ***);
void get_table (char ***);

int main (void) {
char ***maintable;
get_table(maintable);

A C function can not alter in any way the value of it argument.
get_table can't change maintable no matter how many *s the declaration
has.

Step back a bit and think about a function that takes an int:

void f(int);

and a call to it like this:

int x = 42;
f(42);

we know for certain that f can't change x -- C passes by value: the
value of x is passed to f and appears in f as the initial value of a new
object, the declared parameter of the function.

It won't matter if add a star:

void f(int *);
/* ... */
int *x;
f(x);

or if we add several:

void f(int ****);
/* ... */
int ****x;
f(x);

the function can't change x. For a function to change something in the
calling function we must pass a pointer to that thing. This means that
the declaration of the function's parameter will have one more * than
the declaration of object we want changed. This mismatch is corrected
but passing &x rather than x:

void f(int *ip) { *ip = 1; }
/* ... */
int x = 42;
f(&x);

Now x will be 1.
if (maintable == NULL) {
puts("maintable is NULL.");
} else {
puts("maintable is not NULL.");
print_table(maintable);

The fact that you get this is simply an accident.
}
}

void get_table (char ***table) {
static char *functable[2][4] =
{
"A01", "A02", "A03", "A04",
"B01", "B02", "B03", "B04"
};
*table = functable;

If the compiler is not complaining about this line, you must alter the
way you call it. The assignment is between type that can't be assigned
without a diagnostic (basically an error message). The expression

functable

is of type char *(*)[4]. That's quite a complex type (it's a pointer to
an array or 4 pointers to char) but the point is that you can't assign
such an object to a char ** object (that's the type of *table).

Arrays complicate the picture to the extent that I am not exactly sure
how best to fix things. If you really need to have the function alter an
object in the caller so that it refers to a 2D array of strings then
you'll need a seriously complex type![1]

If you said what it is you need to do, maybe someone could come up with
a simpler design.
}

void print_table (char ***ptable) {
int i, j, k;
for (i=0; i < 2; i++) {
for (j=0; j < 4; j++) {
for (k=0; k < 3; k++) {
printf("%c", ptable[j][k]);
}
printf(" ");
}
printf("\n");
}
};


That ; is a syntax error.

[1] Here's one way:

#include <stdio.h>

void print_table (char *(*)[4]);
void get_table (char *(**)[4]);

int main (void) {
char *(*maintable)[4];
get_table(&maintable);

if (maintable == NULL) {
puts("maintable is NULL.");
} else {
puts("maintable is not NULL.");
print_table(maintable);
}
}

void get_table (char *(**table)[4]) {
static char *functable[2][4] =
{
"A01", "A02", "A03", "A04",
"B01", "B02", "B03", "B04"
};
*table = functable;
}

void print_table (char *(*ptable)[4]) {
int i, j, k;
for (i=0; i < 2; i++) {
for (j=0; j < 4; j++) {
for (k=0; k < 3; k++) {
printf("%c", ptable[j][k]);
}
printf(" ");
}
printf("\n");
}
}

but I doubt very much that this is the best way to do what you want.
 
M

Malcolm Nooning

Hiding data within functions is done a lot in higher level languages.
The data is only accessible by invoking the function that hides it.
My original idea was to be able to figure out how to do the same thing
in C. As I stated earlier, I found a way to do it using typedef
string ..., but that did not provide any understanding of why I could
not do it using just chars, arrays of chars, and pointers. Your code
does indeed work. Along with your explanation, that is what I
wanted. Thank you very much.

Besides my two C books, I read a number of pointer and array tutorials
to fully understand these concepts. Only one book covered up to
arrays of arrays of chars, but it did not discuss pointers to arrays
of arrays of chars, or passing the information in such structures to
and from functions. Your code is an excellent example.


For completeness, can you explain why the parenthesis are as they are
with
void print_table (char *(*)[4]);
and
void get_table (char *(**)[4]);

I know that
char (*)[4]
is a pointer to an array of four chars, while
char *[4]
is an array of four pointers to type char.

You have already gone further than the books and tutorials, so again I
thank you for your efforts. I hope the explanation does not get
quantum!

By the way, I will be passing all of this information to book and
tutorial authors, in case they ever update their writings.

Thanks again.
 
B

Ben Bacarisse

Malcolm Nooning said:
For completeness, can you explain why the parenthesis are as they are
with
void print_table (char *(*)[4]);
and
void get_table (char *(**)[4]);

I know that
char (*)[4]
is a pointer to an array of four chars, while
char *[4]
is an array of four pointers to type char.

C types are read from the "inside" out (respecting parentheses) going
right if you can and left otherwise. The "inside" is where the name
would be if the type were being used to declare a variable. Brackets
are used to alter this order -- that's why (*)[...] is a pointer to a
array and *[...] is an array or pointers.

Another way of looking at this is to think of the various components of
a declarator like operators in an expression. In fact, this is why C
types look like they do -- the declarator syntax was chosen to mirror
the syntax of expressions. In this view, function brackets and array
brackets bind more tightly than the * used for pointer declarations with
parentheses being used to alter this binding as they are in expressions.

Given that you understand how ()s alter the meaning of char (*)[4] and
char *[4], I find your opening question hard to answer. The parentheses are as
they are so that the types are correct! Maybe you mean "why are these
the types that work" or maybe "what are these types"?

The answer to the second of these questions is that types are "a pointer
to an array of 4 pointers to char" and "a pointer to a pointer to an
array of 4 pointer to char". As you'd expect from the simple int
example I gave, the type needed so that get_table can modify the value
of the variable in the caller has one more "pointer to ..." in front of
it. So the first question becomes: "why are maintable in main and the
parameter of print_table of type "pointer to an array of 4 pointers to
char"?

The data are held in a 2D array, and arrays in C are passed as pointers
to their first element. Ignoring the element type for the moment (it's
char * but let's just say T for now) an object declared:

T x[2][4];

will be passed as a pointer to the first element -- a pointer to the
first of the two 4-element sub-arrays of x:

print_table(T (*)[4]);

T is in fact char * which is why the real type you need is

print_table(char *(*)[4]);

That's also the type you need for the variable that refers to the actual
data you have. That's why maintable is declared

char *(*maintable)[4];

I hope that helps.

<snip>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,901
Latest member
Noble71S45

Latest Threads

Top