3

I've been fiddling around to see if there's any way to retain information about an array's length automatically when passed into a function (see my other question here: Why is this array size "workaround" giving me a warning?), but my question is more about a warning that gcc is giving that doesn't make sense to me.

According to this website (EDIT: I misread the website), char *p[10] declares a pointer to a 10-wide array of chars. But when I tried to pass in a pointer to an array into a function, I got this error message from the compiler:

enter image description here

Here is the rest of the program:

enter image description here

I know that when an array is passed into a function, it decays into a pointer (losing information about its length), but it seems that the declaration itself is decaying. What's going on here?

EDIT: When I replace the char *p[10] with char (*p)[10], it doesn't give the warning anymore, and more importantly, it displays the proper array length: 10. I guess my questions are 1) Why do the parentheses change things? and 2) Is this a well-known workaround or am I relying on some behavior of the compiler that isn't guaranteed? (i.e. that array length info can be passed by indirectly passing in a pointer to it?)

Community
  • 1
  • 1
Chris Middleton
  • 5,654
  • 5
  • 31
  • 68
  • 1
    "According to this website, char *p[10] declares a pointer to a 10-wide array of chars." - you misread, it's an array of 10 `char*`. – Mat Mar 14 '14 at 10:25
  • 1
    `char *p[10]` is an array of 10 pointers, not "a pointer to a 10-wide array of chars" (that would be `char (*p)[10]`) – pmg Mar 14 '14 at 10:25
  • Whoops. You're both right, I misread it. Thanks. Any insight into the topic of my edit? – Chris Middleton Mar 14 '14 at 10:26

6 Answers6

4

In fact char *p[10] is an array, of length 10, of pointers to char. You are looking for char (*p)[10]. That is a pointer to an array, of length 10, of char.

You might find http://cdecl.org/ a useful resource to help you test your understanding of declarations.

Regarding the discussion surrounding dynamic arrays, you are going to have to accept that once you allocate an array dynamically, the system provides no means for you to recover the length of the array. It is your responsibility to remember that information.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Can anyone explain my downvote? – David Heffernan Mar 14 '14 at 10:30
  • Wasn't me! I actually upvoted your answer. While I'm commenting, do you have any insight into the content of the section I added below? The "workaround"? Thanks for your answer and the website btw. – Chris Middleton Mar 14 '14 at 10:30
  • If you wish to pass a parameter that is an array of a fixed length, you can certainly do that, as you have now discovered. But I guess you want to retain length information for dynamically allocated arrays? – David Heffernan Mar 14 '14 at 10:32
  • Yeah, that's sort of what I'm getting at. What do you make of the second program in this post: http://stackoverflow.com/a/22346692/2407870, which does return the correct length at runtime, but does it without explicit dynamic memory allocation? – Chris Middleton Mar 14 '14 at 10:36
  • That's using VLAs (variable length arrays). A feature that should be used with extreme caution. Quite different from dynamic memory allocation. You cannot extend the life of a VLA beyond the function in which is was declared. And VLAs are a neat way to generate runtime stack overflows. You are going to have to accept that you need to explicitly maintain the length of the array, and pass it around with your array. There is simply no escape for that. – David Heffernan Mar 14 '14 at 10:38
  • Thanks for the insight. I will have to read up on the implementation of VLAs, though I'd assume it's fairly involved. With regard to the topic of this post, is it reliable behavior that passing a pointer to an array into my function allows me to access the length of the array when I dereference it? It seems like a useful construct so I'm curious as to why it isn't used more often. – Chris Middleton Mar 14 '14 at 10:43
  • You really don't want to use VLAs. They are an advanced feature with a small number of uses. Dynamic memory allocation is not one of them. You might well want to avoid remembering the length of the array. Who wouldn't? But you cannot avoid remembering the length. It's not possible. That's really all there is to say about it. – David Heffernan Mar 14 '14 at 10:45
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/49734/discussion-between-amadeusdrzaius-and-david-heffernan) – Chris Middleton Mar 14 '14 at 10:47
2

The subject of your question has been answered already but I wanted to address the heart of it, which is "can I encode the length of an array in its type?" Which is in fact what a pointer-to-array does. The real question is whether you can actually gain any brevity or safety from this. Consider that in each scope where you have a declaration of your type, the length still needs to be known a-priori. To show you what I mean let's generalize your example slightly by making 10 a compile-time constant N.

#define N 10

size_t arraylength(char (*arrayp)[N]) {
    return sizeof(*arrayp);
}

int main(void) {
    char array[N];
    assert( arraylength(&array) == N ); //always true
}

So far so good. We didn't have to pass the length of array anywhere. But it's easy to see that anywhere the expression sizeof(*arrayp) is used, we also could have written N. And any place we declare a char(*)[ ], the bracketed length must come from somewhere.

So what if N isn't a compile time constant, and array is either a VLA or a pointer-to-array from malloc? We can still write and call arraysize, but it looks like this:

size_t arraylength(size_t N, char (*arrayp)[N]) {
    return sizeof(*arrayp);
}

int main(void) {
    size_t N = length_from_somewhere();
    char array[N];
    assert( arraylength(sizeof(array), &array) == N );
}

In defining arraysize N must still be visible before the declaration of arrayp. In either case, we can't avoid having N visible outside of the declaration of arrayp. In fact, we didn't gain anything over writing arraysize(size_t N, char* array) and passing array directly (which is a bit silly given the purpose of this function.) Both times arraylength could have equally been written return N;

Which isn't to say that array pointers are useless as parameters to functions -- in the opposite situation, when you want to enforce a length, they can provide type checking to make sure somefunc(char (*)[10]); receives a pointer to an array that is really (sans shady casting) 10 elements long, which is stronger than what a construct like [static 10] provides.

Also keep in mind that all of the length measurements above depend on the underlying type being char where length == size. For any larger type, taking the length requires the usual arithmetic e.g.

sizeof(*arrayp)/sizeof((*arrayp)[0])

tab
  • 903
  • 1
  • 6
  • 8
1

In C, arrays decay to pointers to their first elements on most uses. In particular, what a function receives is always just a pointer to the first element, the size of the array is not passed with it.

Get a good text on C and read up on arrays.

vonbrand
  • 11,412
  • 8
  • 32
  • 52
1

I've been fiddling around to see if there's any way to retain information about an array's length automatically when passed into a function

The problem is so annoying that lots of programmers would love to have an answer. Unfortunately, this is not possible.

It seems that the declaration itself is decaying

Pointer to an array is not the same as a pointer to a pointer; that is why you are getting an error.

There is no decaying going on in your code, because you are not passing an array in your code sample: instead, you are trying to pass a pointer to an array &p. The pointer to an array of characters is not compatible to the expected type of the function, which is char**. Array size from the declaration is ignored.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
1

You need to keep in mind two things:
1. Arrays are not pointers.
2. Array names decays to pointers (in most cases) when passed as arguments to functions.

So, when you declare

int a[10];  // a is an array of 10 ints
int *b;     // b is a pointer to int  

both of a and b are of different types. Former is of type int [10] while latter is of type int *.

In case of function parameter

void foo1 (int a[10]); // Actually you are not passing entire array 
void foo2 (int a[]);   // And that's why you can omit the first dimension.
void foo3 (int *a);    // and the compiler interprets the above two third  

ain all of the above function declarations is of same data type int *.

Now in your case

unsigned long arraySize(char *p[10]);  

you can declare it as

unsigned long arraySize(char *p[]);  

and hence

unsigned long arraySize(char **p);  

All are equivalent.

char *p[10] char *p[] and char **p all are exactly equivalent but when they are declared as parameter of a function otherwise char *p[10] (an array of 10 pointers to char) and char **p (a pointer to pointer to char)are entirely of different type.

Suggested reading: C-FAQ: 6. Arrays and Pointers explains this in detailed.

haccks
  • 104,019
  • 25
  • 176
  • 264
0

Array name itself is a constant pointer. for example int arr[10]={0}; arr contains the address of arr[0]. hence arr equals&arr[0] . when u pass the arraysize(&p) , you are actually passing a double pointer . The correct format to pass a array pointer would be arraysize(&p[0]) or arraysizeof(p)

Note Array name is constant pointer , you cant change its value . int arr[10]; arr++; is invalid.

In your case you cant find a size of an array in function by passing the array name . it would return size of pointer(4 or 8 depends on your processor . The method is to pass the size along with the array func(array_name , array_size);

  • Thanks for your answer. As I mentioned in my edit, passing a pointer to the array and then dereferencing that pointer does give the correct array size of 10. Do you think that's reliable behavior? – Chris Middleton Mar 14 '14 at 10:46
  • when you dereference a pointer to array in a function , it treats the argument as a pointer only . If you need array size inside the function dont dereference the pointer instead pass the size of the array along with the function . `void funct (int * arr_ptr ,int arr_size; – Vignesh K Viki Mar 14 '14 at 10:58
  • I'd beg to differ. Try this code out for yourself! :) #include unsigned long arraySize(char (*p)[10]); int main(void){ char p[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; printf("%lu\n", arraySize(&p)); return 0; } unsigned long arraySize(char (*p)[10]){ return sizeof ( *p ); } – Chris Middleton Mar 14 '14 at 10:59