array of pointers

M

Mark

Hello

drilling the pointers in K&R 2nd edition. Dazzled and confused with pointers
and arrays of pointers. Trying to figure out why this code doesn't work
properly (want to print out the elements of the array):

#include <stdio.h>

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz"};

int main(void)
{
while (*lineptr) {
printf("string '%s'\n", *lineptr++); /*XXX*/
}

return 0;
}

Compiler is not happy with XXX line: invalid lvalue in increment
My undestanding is that 'lineptr' is an array of pointers, 'lineptr' itself
is pointing on the first entry, so why can't we just move along the array
just increasing this pointer one by one? Seems like ANSI C doesn't agree
with me :)

But changing code to this makes it compiled and run:

#include <stdio.h>

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz"};

int main(void)
{
const char **tmp = lineptr;
while (*tmp) {
printf("string '%s'\n", *tmp++);
}

return 0;
}

I have gut feeling this latter snippet is wrong either. Could you help to
clearly understand what's going on and where I'm wrong.

-- Mark
 
V

vippstar

Hello

drilling the pointers in K&R 2nd edition. Dazzled and confused with pointers
and arrays of pointers. Trying to figure out why this code doesn't work
properly (want to print out the elements of the array):

#include <stdio.h>

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz"};

int main(void)
{
while (*lineptr) {
printf("string '%s'\n", *lineptr++); /*XXX*/
}

return 0;

}

Compiler is not happy with XXX line: invalid lvalue in increment
My undestanding is that 'lineptr' is an array of pointers, 'lineptr' itself
is pointing on the first entry, so why can't we just move along the array
just increasing this pointer one by one? Seems like ANSI C doesn't agree
with me :)

lineptr is an ARRAY of pointers. Which means, it's an array. You can't
increment it, because it's not a pointer. (it simply becomes a pointer
in expressions)
Consider this invalid example, which might be more obvious:

#include <stdio.h>

int main(void) {
int m[] = { 1, 2, 3 };
int *p = m;
size_t i;
for(i = 0; i < 3; i++)
printf("%d\n", *m++); /* change m to p, and this will work */
return 0;
}
But changing code to this makes it compiled and run:

#include <stdio.h>

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz"};

int main(void)
{
const char **tmp = lineptr;

As I said, lineptr becomes a pointer in expressions. It's type is
static const char *[5], but becomes static const char **.
while (*tmp) {

If you want this to work, the last pointer in the lineptr array must
be NULL.
Change lineptr to:
static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz", NULL };
printf("string '%s'\n", *tmp++);
}

return 0;

}

I have gut feeling this latter snippet is wrong either. Could you help to
clearly understand what's going on and where I'm wrong.

I hope my comments help.
 
B

Ben Bacarisse

Mark said:
drilling the pointers in K&R 2nd edition. Dazzled and confused with pointers
and arrays of pointers. Trying to figure out why this code doesn't work
properly (want to print out the elements of the array):

#include <stdio.h>

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz"};

int main(void)
{
while (*lineptr) {
printf("string '%s'\n", *lineptr++); /*XXX*/
}

return 0;
}

Compiler is not happy with XXX line: invalid lvalue in increment
My undestanding is that 'lineptr' is an array of pointers,

Indeed it is.
'lineptr' itself
is pointing on the first entry,

Here is the problem. 'lineptr' itself is an array (as you've said).
It is not a pointer to anything. In particular it is not a pointer
variable. You have been led astray by the fact that, for your
convenience, C converts an array to a pointer to its first element in
almost all cases (the exceptions don't matter for now).
so why can't we just move along the array
just increasing this pointer one by one? Seems like ANSI C doesn't agree
with me :)

C does not let you treat an array like an assignable thing. You can't
put an array on the left hand side of an assignment, and you can't
increment one either. The conversion to a pointer value does not help
you get round this because values aren't modifyable any more than
arrays are.
But changing code to this makes it compiled and run:

#include <stdio.h>

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz"};

int main(void)
{
const char **tmp = lineptr;
while (*tmp) {
printf("string '%s'\n", *tmp++);
}

return 0;
}

I have gut feeling this latter snippet is wrong either.

Well, yes, but it is absolutely fine as far as the pointer and the
increment are concerned. The trouble is that you rely on the happy
accident that there happens to be a null pointer after the last
element of the array. When tmp is incremented to point to the char *
after "xyz" all hell could break loose (the dreaded undefined
behaviour) because you don't have the right to access anything after
the end of an array.

The solution is to put a null pointer in the array itself there so
*tmp is predictably false when you want it to be:

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz", NULL};
 
R

Richard Tobin

printf("string '%s'\n", *lineptr++); /*XXX*/
[/QUOTE]
lineptr is an ARRAY of pointers. Which means, it's an array. You can't
increment it, because it's not a pointer. (it simply becomes a pointer
in expressions)

.... and indeed it does become a pointer in this case, but as the
standard says (C99 6.3.2.1) it is "converted to [a pointer] and is not
an lvalue".

-- Richard
 
V

vippstar

lineptr is an ARRAY of pointers. Which means, it's an array. You can't
increment it, because it's not a pointer. (it simply becomes a pointer
in expressions)

... and indeed it does become a pointer in this case, but as the
standard says (C99 6.3.2.1) it is "converted to [a pointer] and is not
an lvalue".

Well, that's right. He can't increment it because it's not a pointer
lvalue.
Thanks for the correction.
 
A

Andrey Tarasevich

Mark said:
...
#include <stdio.h>

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz"};

int main(void)
{
while (*lineptr) {
printf("string '%s'\n", *lineptr++); /*XXX*/
}

return 0;
}

Compiler is not happy with XXX line: invalid lvalue in increment
My undestanding is that 'lineptr' is an array of pointers,

Absolutely correct.
'lineptr' itself is pointing on the first entry,

Well, no. 'lineptr' itself is an _array_. It is an aggregate object, a
"large" object, an object consisting of many smaller objects "glued"
together. In this case the smaller objects are 'const char*' pointers
and the array is a compact block of 5 of these 'const char*' pointers.

However, when you use 'lineptr' it in an expression, most of the time it
will get _implicitly_ _converted_ to a pointer type for you. The pointer
type in this case is 'const char **' (a pointer to an element) and the
result of the conversion points to the first element of the array. In
other words, most of the time you write

lineptr

in your code, the compiler will interpret it as if you wrote

(const char **) lineptr

i.e. it work as if the compiler inserted an invisible cast operator into
the code.

Alternatively and equivalently, you can think of it as if every time you
write 'lineptr' in your code, the compiler will interpret it as if you
wrote

&lineptr[0]

For example, when you write

printf("string '%s'\n", *lineptr)

it is really interpreted as

printf("string '%s'\n", *(const char **) lineptr)
printf("string '%s'\n", *&lineptr[0])

(choose any of the two, whichever interpretation you prefer).
so why can't we just move along the array
just increasing this pointer one by one?

The result of the above implicit conversion to 'const char **', of
course, is a pointer. But this pointer (just like a result of any
conversion) is just an rvalue. It cannot be modified. In C rvalues
cannot be modified. For example, if you declare

int i = 0;

you can modify 'i'

i++; /* OK */

but you can't modify the result of any conversion preformed on 'i'

((double) i)++; /* ERROR */
((int) i)++; /* ERROR */

and you can't modify the result of '&' operator on 'i'

(&i)++; /* ERROR */

Why? Because both the result of the conversion and the result of '&'
operator are rvalues. They cannot be modified. For the very same reason,
if you try

lineptr++;

the compiler sees it as

((const char **) lineptr)++; /* ERROR */

and it fails for the very reason described above.

Alternatively, as I said above, if you prefer, you can think of it as

(&lineptr[0])++; /* ERROR */

and it fails for the reason described above.
But changing code to this makes it compiled and run:

#include <stdio.h>

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz"};

int main(void)
{
const char **tmp = lineptr;
while (*tmp) {
printf("string '%s'\n", *tmp++);
}

return 0;
}

I have gut feeling this latter snippet is wrong either. Could you help to
clearly understand what's going on and where I'm wrong.

This code is OK with regard to the above issue. This time you took the
result of the implicit conversion and stored it in a separate _variable_
'tmp'. Your

const char **tmp = lineptr;

is, again, equivalent to

const char **tmp = (const char **) lineptr;
const char **tmp = &lineptr[0];

which is perfectly valid. But now you have an object, a variable 'tmp',
which is a _modifiable_ _lvalue_. You _can_ increment an lvalue, which
is why your code will compile and do what you expect it do do.

Returning to our example with 'i', you can't increment the result of the
conversion

((double) i)++; /* ERROR */

but you can copy the result to a separate variable and increment the latter

double d = (double) i;
d++; /* OK */

which is basically what you do in the second version of the code.

Just note though, that in both versions of the code your cycle is
supposed to terminate when you hit a null-pointer in the array. There is
no null-pointers in your array, so your cycle will never terminate. It
go out of bounds and produce undefined behavior. You have to either add
the terminating null element explicitly

static const char *lineptr[] = {"abc", "d", "ef", "jh", "xyz", NULL};

or specify the larger array size explicitly

static const char *lineptr[10] = {"abc", "d", "ef", "jh", "xyz"};

(in the latter case the extra non-initialized elements will be
zero-initialized by the compiler).
 
M

Mark

Hello guys.

Thanks a lot for your priceless explanations!

AT> Well, no. 'lineptr' itself is an _array_. It is an aggregate object, a
AT> "large" object, an object consisting of many smaller objects "glued"
AT> together. In this case the smaller objects are 'const char*' pointers
AT> and the array is a compact block of 5 of these 'const char*' pointers.
[skip]

In this thread I'd like to clarify one more pointers related question.
What's the point and need to exploit pointer to array, when the same thing
can be basically achieved with a plain pointer? Let's consider this:

int a[5];
int *ip;
ip = a;

Now we can access each element of a[] as *ip, *(ip + 1), *(ip + 2) etc. This
is fine.

Now let's turn to:

int a[5];
int (*ip)[];
/* or */
int (*ip)[5]; /* I think both are fine in this particular case? */
ip = &a;

Now we can address each element of a[] as *(*ip + 1), *(*ip + 2) and so
forth. It seems a lot more complicated to do this than in the former case.
What can be he benefit of using such representation?


-- Mark
 
A

Andrey Tarasevich

Mark said:
In this thread I'd like to clarify one more pointers related question.
What's the point and need to exploit pointer to array, when the same thing
can be basically achieved with a plain pointer?

It is _mostly_ the same thing, but not _exactly_ the same thing.
Let's consider this:

int a[5];
int *ip;
ip = a;

Now we can access each element of a[] as *ip, *(ip + 1), *(ip + 2) etc. This
is fine.

Yes. You can also access them as 'ip[0]', 'ip[1]', 'ip[2]' etc.

Moreover, whenever you access the original array using the '[]'
operator, as in

a[2]

the compiler really interprets it as

*(a + 2)

and, as I said in my previous message, the array gets implicitly
converted to a pointer, so the previous is interpreted as

*((int*) a + 2)

I.e. in C the '[]' is just a shorthand for the combination of unary '*'
and binary '+'. The only difference between '*(ip + 2)' and '*(a + 2)'
is that in the former case the pointer value is stored in a pointer
object 'ip', while in the latter case the pointer value is not stored
anywhere, it is generated "on the fly" as a temporary value - the result
of the implicit array-to-pointer conversion.
Now let's turn to:

int a[5];
int (*ip)[];
/* or */
int (*ip)[5]; /* I think both are fine in this particular case? */

Yes, except that in the first case you have a pointer to an incomplete type.
Now we can address each element of a[] as *(*ip + 1), *(*ip + 2) and so
forth.

Right. Or, more naturally, as '(*ip)[0]', '(*ip)[1]', '(*ip)[2]' etc.
It seems a lot more complicated to do this than in the former case.
What can be he benefit of using such representation?

The benefit of using the 'int (*ip)[5]' form is that in this case the
"arrayness" of the original array is not lost. It is preserved in the
pointer type. The result of dereference - '*ip' - still has the array
type - 'int[5]'. Why is it important? As I side before, in _most_ cases
array type decays to pointer type, but not always. For example, when you
apply the 'sizeof' operator to an array, you get the full size of the
array, not the size of a pointer. This happens because the
array-to-pointer conversion is not applied to arguments of 'sizeof'.
This means that with 'int (*ip)[5]' you can do

sizeof *ip / sizeof **ip

and get 5 as the result - the number of elements in the array. Also,
pointer 'ip' in this case is strictly typed - it can only point to
arrays of type 'int[5]', but not to 'int[3]' or 'int[10]'. This might be
useful at times.

With 'int *ip' the "arrayness" of the original array is lost completely.
Now it is just a pointer. The above 'sizeof' trick won't work with it.
But the good news is that it can point to an array of any size. This is
very useful, if used correctly.

As for 'int (*ip)[]' - I don't see much use for it considering that its
"flexibility" (with regard to size) is already present in a plain 'int
*ip'. It could be useful in non-defining declarations, but probably not
as a definition (maybe I'm missing something).

P.S. I'd also suggest that you visit the corresponding section of the FAQ

http://c-faq.com/aryptr/index.html
 
B

Barry Schwarz

Hello guys.

Thanks a lot for your priceless explanations!

AT> Well, no. 'lineptr' itself is an _array_. It is an aggregate object, a
AT> "large" object, an object consisting of many smaller objects "glued"
AT> together. In this case the smaller objects are 'const char*' pointers
AT> and the array is a compact block of 5 of these 'const char*' pointers.
[skip]

In this thread I'd like to clarify one more pointers related question.
What's the point and need to exploit pointer to array, when the same thing
can be basically achieved with a plain pointer? Let's consider this:

int a[5];
int *ip;
ip = a;

Now we can access each element of a[] as *ip, *(ip + 1), *(ip + 2) etc. This
is fine.

Now let's turn to:

int a[5];
int (*ip)[];
/* or */
int (*ip)[5]; /* I think both are fine in this particular case? */
ip = &a;

Now we can address each element of a[] as *(*ip + 1), *(*ip + 2) and so
forth. It seems a lot more complicated to do this than in the former case.
What can be he benefit of using such representation?

For a one dimensional array, Andrey pointed out one benefit. Now
consider what happens with a two dimensional array
char b[7][5];
and potentially useful statements like
for (ip = b; ip < sizeof b / sizeof *b; b++)
 

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

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top