math4tots
math4tots

Reputation: 8869

arrays with missing sizes vs pointers

I have generally thought that the following two prototypes were interchangeable:

int main(int argc, char ** argv);
int main(int argc, char * argv[]);

In general I had imagined that char ** argv and char * argv[] were interchangeable. However, I have also come accross some stuff on the internet that claim that you can declare structs like

struct S {
  int size;
  int ar[];
};

And then simply malloc appropriately so that ar can be as large as you want at runtime.

But this seems rather strange to me. If I had instead declared

struct S {
  int size;
  int * ar;
};

Can I still do the same thing? I would have imagined this depends on what you make ar point to.

How exactly are int * ar and int ar[] different when used inside a struct? What about with char ** argv and char * argv[] in function prototypes? Do they have different semantics in C as opposed to in C++?

Upvotes: 1

Views: 88

Answers (4)

Keith Thompson
Keith Thompson

Reputation: 263567

There's a special-case rule for function parameters that look like arrays. Any such parameter is "adjusted" to a pointer to the (possibly qualified) element type.

Because of this rule, these definitions:

int main(int argc, char **argv) { /* ... */ }

and

int main(int argc, char *argv[]) { /* ... */ }

are exactly equivalent. (There's a similar rule for parameters of function type, which are adjusted to function pointers.)

This rule applies only to parameter declarations.

One annoying consequence is that if you declare an array parameter with a size, it's silently ignored:

void func(int array[42]);

really means

void func(int *array);

(There is a usage of the static keyword, added in C99, which I won't go into here.)

struct S {
    int size;
    int ar[];
};

This is a flexible array member, a feature added in C99. It declares that ar is an array (not, I repeat not, a pointer) of unspecified size. To use it, you have to allocate enough space to hold however many elements you're going to need at run time. This was added to the language as a replacement for the "struct hack", described in question 2.6 of the comp.lang.c FAQ.

Section 6 of the same FAQ is an excellent resource for explaining the often confusing relationship between arrays and pointers.

Upvotes: 4

michaelmeyer
michaelmeyer

Reputation: 8215

You can use both the first and the second layout for the same purpose, but the layout in memory will not be the same. For the first example, you'd have (assuming a 32 bit architecture):

[size (4 bytes)][ar (size bytes)]

And for the second one:

[size (4 bytes)][pointer to ar (4 bytes)][ar (size bytes)]

So the second solution wastes memory.

Upvotes: 1

danielschemmel
danielschemmel

Reputation: 11126

There is a special rule for using arrays types in function arguments: They become pointer types. However, that rule only holds for function arguments.

This becomes very obvious when you replace the undefined size with, e.g. a size of two:

void f(int x[2]); // equivalent to void f(int* x);

struct A {
    int q[2]; // obviously not equivalent to int* q;
};

The structure definition you used

struct S {
    int size;
    int ar[];
};

is intended to say that an arbitrary amount of ints is supposed to follow the size member - probably exactly size elements. You cannot just leave out the last member, because that could lead to errors w.r.t. alignment and padding (assume for a second that ar was an array of doubles, and you will see the problem).

This syntax is an evolution of an old trick and has been added in C99.

Upvotes: 4

kec
kec

Reputation: 2149

char ** and char *[] are indeed the same. In fact, the below will work:

void foo(int a1[]) {
    int a2[3];
    a2 = 0; // ERROR: Can't assign to an array.
    a1 = 0; // OKAY: You can assign to it, because it's actually a pointer!
    ...

The struct S that you show is using a flexible array member, though. It takes up no space. You can't assign to it. Basically, int [] means different things depending on whether it is a member or a parameter. In the struct where you have the pointer, though, you can basically make that point anywhere.

Upvotes: 0

Related Questions