2

I had a bit of a confusion. Below is a very simple example which works:

#include <stdlib.h>

typedef struct 
{
  unsigned char one: 1;
  unsigned char two:1;
  unsigned char three: 1;
  unsigned char four: 1;
} nibble_bits;

typedef union
{
  unsigned char all : 4;
  nibble_bits bits;
} nibble;

void initArr(nibble ** arrLoc, unsigned int size)
{
  nibble * start = arrLoc[0];
  int i =0;
  for (i=0; i<size; i++)
    {
      start[i].all = 0;
    }
}

int main()
{
  nibble * fourNibbles = (nibble *) malloc(4 * sizeof(nibble));
  initArr(&fourNibbles,4);
}

This compiles fine with no warnings. However, when I change the first line in main:

nibble * fourNibbles = (nibble *) malloc(4 * sizeof(nibble));

to:

nibble fourNibbles[4];

I get the following:

warning: main.c: In function ‘main’: main.c:150: warning: passing argument 1 of ‘initArr’ from incompatible pointer type

Upon running, I get a "Bus error 10".

Seems to me like the lines are doing the same thing, except that the malloc is allocating space for the array on the heap and the array declaration is on the stack. But (I thought) either way "fourNibbles" is of type "pointer to nibble", and hence the address of "fourNibbles" would be pointer to pointer to nibble (nibble **).

What am I missing here?

heisenBug
  • 865
  • 9
  • 19

2 Answers2

3

These are not even remotely the same. This

nibble * fourNibbles = (nibble *) malloc(4 * sizeof(nibble));

declares a pointer fourNibbles, while this

nibble fourNibbles[4];

declares an array. Arrays and pointers are two completely different things, which (at object level) have nothing in common. Trying to use them interchangeably in object contexts (like & operator) will only lead to disaster. There lots of information on this topic here on SO (search for "array pointer difference") as well as in this [de-facto standard] C FAQ: http://c-faq.com/aryptr/index.html

There is another thing that draws attention in your code though. Your function

void initArr(nibble ** arrLoc, unsigned int size)

is specifically tailored to the first variant, since it requires a pointer to a pointer as its first argument. It will not work if you attempt to force a pointer to an array to the first argument (which you already had a chance to observe firsthand).

However, the real question here is why your initArr function is written in such a bizarre way. This sequence

void initArr(nibble ** arrLoc, unsigned int size)
{
  ...
  nibble * start = arrLoc[0];
  ...
    start[i].all = 0;

looks rather unusual. Why are you passing a pointer to a pointer instead of an ordinary single-level pointer? E.g. you could simply do

void initArr(nibble *start, unsigned size)
{
  unsigned i;
  for (i = 0; i < size; ++i)
    start[i].all = 0;
}

This version would be called as

initArr(fourNibbles,4); /* note: no `&` operator */

and it would be compatible with both malloc-ed arrays and explicitly declared arrays.

P.S. In C language a better idiom for malloc is

nibble * fourNibbles = malloc(4 * sizeof *fourNibbles);

Note that in this variant type name nibble is mentioned only once.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • I'm not actually using the methods described; I tried to provide a simple example illustrating the problem. The actual application is a multi-threaded horrible monster with multi-dimensional arrays, but the problem remains the same. Maybe the motivation here is less clear, but I think the problem is obvious. – heisenBug Sep 06 '13 at 17:29
  • @heisenBug: In that case, i.e. if you really need a pointer-to-pointer parameter, you will not be able to directly pass arrays as arguments. It is not possible. In that case, if you have to pass an array declared as `nibble fourNibbles[4];`, you have to declare an additional pointer `nibble * temp = fourNibbles;` and pass `&temp` instead. There's no way around it. – AnT stands with Russia Sep 06 '13 at 17:34
  • Does the `nibble *temp = fourNibbles;` trick work? It pacifies the compiler by sorting out the types, but if the called function is `function(nibble **data)` and it accesses anything other than `data[0]`, then all hell is going to break loose because `temp` is not actually an array. – Jonathan Leffler Sep 06 '13 at 17:38
  • @Jonathan Leffler: I don't really see it as a "trick". But, of course, it can only work if we only access `data[0]` (i.e. do `*data`). But if we have to access `data[1]` and greater, then we have to prepare a larger "index array" in the calling code, no argument here. E.g. if necessary we can do `nibble *temp[5] = { fourNibbles, another_fourNibbles, yet_another_fourNibbles, ... };` and call it as `function(temp)`. Anyway, whether it is necessary or not depends on the details the OP did not provide. – AnT stands with Russia Sep 06 '13 at 17:42
2

You are missing that the address of an array has a different type from the pointer that the plain array name becomes when used in an expression.

That is:

int *a1 = ...;
int a2[] = { ... };

some_func(&a1);
some_func(&a2);

cannot be correct unless some_func() expects a void *. The first call passes an int ** — a pointer to pointer to int; the second call passes an int (*)[] — a pointer to array of int. Drop the & from the array.

However, in your code, the problems are more complex. Because the function expects a nibble **, you have problems. What you should be doing is passing a nibble *:

void initArr(nibble *arrLoc, unsigned int size)
{
    for (unsigned int i = 0; i < size; i++)
        start[i].all = 0;
}

int main(void)
{
    nibble *fourNibbles_1 = (nibble *) malloc(4 * sizeof(nibble));
    nibble fourNibbles_2[4];
    initArr(fourNibbles_1, 4);
    initArr(fourNubbles_2, 4);
    initArr(&fourNubbles_2[0], 4);
}

Your actual code is doing some really rather weird stuff. How much damage it is doing may depend on how big a pointer is compared to a nibble.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Why is it necessary that the array address have a different type than the "pointer that the plain array name becomes when used in an expression"? Both the address of the array and the address of the pointer are pointers to the same sized/typed pieces of data, and both pointers themselves should be the same size in memory. – heisenBug Sep 06 '13 at 17:26
  • It is necessary because the C standard says that the type of the address of an array is different from the pointer that the plain name of an array becomes. The difference is that given `int x[4] = { 0, 1, 2, 3 }; int (*a)[4] = &x; int *b = a; int *c = &a[0];`, using `b[1]` and `c[1]` addresses the element of `x` with the value 1, but `a[1]` addresses the array after `x` (which isn't defined, so is bad). That is, the size of the object pointed at by `a` is `sizeof(int[4])`, but the size of the objects pointed at by `b` and `c` is `sizeof(int)`. – Jonathan Leffler Sep 06 '13 at 17:30
  • 2
    @heisenBug: Absolutely not. Array is not a pointer. When you apply `&` to the array, you get a pointer to the beginning of the array itself (i.e. numerically that pointer points to the location of the first element). You do not get "a pointer to a pointer" as in the first case. So, your assumption that "both pointers point to the same type/size of data" is completely incorrect. – AnT stands with Russia Sep 06 '13 at 17:31
  • 1
    @heisenBug to state in other words what Jonathan said: the problem is not that the two pointers don't point to the same "memory address", but that the exact type is needed for pointer arithmetic, which is also used to index an array (since a[i] is equivalent to *(a+i)). That "+i" means "go ahead by i elements", so you must know the size of an element (and this is inferred from the type). – LorenzoDonati4Ukraine-OnStrike Sep 06 '13 at 19:42