2

I have read this post.
From that, I read this:
From C99: 6.2.7.27

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.39) Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements. (emphasis mine)

My interpretation of the parts important to me for the purpose of this question seem to say
if I have:

int *a, **b;   

registration and alignment are guaranteed, and that all of these statements are true;

sizeof(a)==sizeof(*a)&&
sizeof(int *)==sizeof(b)&&
sizeof(*b)==sizeof(**b);// all essentially int pointers,
                        // and would be equal

but if I have:

int *a;
float*b;  

registration and alignment are not guaranteed. i.e.:

sizeof(a)!=sizeof(b)&&
sizeof(float *)!=sizeof(int *)&&
sizeof(*b)!=sizeof(*a);//all pointers, but not of compatible types
                       //therefore not guaranteed to be equal.

The reason I ask is because of this discussion,
where I posted an answer showing a function that creates a 3D array:

int *** Create3D(int p, int c, int r) 
{
    int ***arr;
    int    x,y;

    arr = calloc(p, sizeof(arr)); 
    for(x = 0; x < p; x++)
    {
        arr[x] = calloc(c ,sizeof(arr)); 
        for(y = 0; y < c; y++)
        {
            arr[x][y] = calloc(r, sizeof(int));
        }
    }
    return arr;
}

Is the following statement safe in terms of using sizeof()?

arr = calloc(p, sizeof(arr)); 

Or, even though only int types are used, should it be:

arr = calloc(p, sizeof(int **));

or:

arr = calloc(p, sizeof *arr);

The question:
Given arr is declared as int ***:
For allocating memory, as long as type stays int is there any danger of using any of the variations of int pointer (int *, int **, arr, *arr, int ***) as the argument to sizeof ?

Is one form preferred over the other? (please give reasons other than style)

Community
  • 1
  • 1
ryyker
  • 22,849
  • 3
  • 43
  • 87
  • 2
    Why should an `int` and a pointer have the same size? The citation does not even mention non-pointer types. And an `int ***` is **not** 3D array, but a pointer to pointer to pointer to `int`! Note: Being a 3-star programmer is not a compliment. – too honest for this site Oct 01 '15 at 21:14
  • @Olaf - If I suggested that an `int` and pointer have the same size, it was unintended. (by the way, point out to me from what exact part of my post did you infer this?) My real question is if within a type, is it safe to use any form of an int pointer to initialize memory. I.e., `int *, int *** and int ****` will all yield the same value for sizeof(). – ryyker Oct 01 '15 at 21:34
  • That was not a suggestion, but an implication: "and that all of these statements are true; `sizeof(a)==sizeof(*a)` ...". You are comparing apples with oranges. A pointer to `int` is not the same type like a pointer to pointer, etc. The `*` is no qualifier, specifier, etc., – too honest for this site Oct 01 '15 at 21:37
  • @Olaf, note, that `a` in the context I am using it was created as, and remains, an `int ***`, not an `int`. Therefore, anecdotally speaking, when I have compared sizes of the two, `sizeof(a)==sizeof(*a)`, has been a true statement. In terms of size, I would expect `a` to have the same size as `int ***`. – ryyker Oct 01 '15 at 21:57
  • 1
    Unless you use a very weird non-standard "C" compiler, `int *a` can be nothing else than a "pointer to `int`". Thus `*a` is an `int`, nothing else. (Note that `sizeof` does not imply and type-compatibility or identity. `sizeof(char [4]`) == sizeof(float)` does not yield any information about same alignment or representation, not even about the representable range, but only about the - well - **size**). E.g. `sizeof(unsigned char) == sizeof(unsigned int)` is well possible with `UCHAR_MAX == 255` and `UINT_MAX == 65535`. – too honest for this site Oct 01 '15 at 22:18
  • @Olaf - you are correct. I see now that `*a` is an int. Also good point on interpreting results of `sizeof` properly. Thanks. – ryyker Oct 02 '15 at 15:37

2 Answers2

4

My interpretation of the parts important to me for the purpose of this question seem to say if I have:

int *a, **b;   

registration and alignment are guaranteed,

a is a pointer to int. b is a pointer to int *. These are not compatible types, and the standard does not require pointers to these types to have the same representation or alignment.

and that all of these statements are true[:]

sizeof(a)==sizeof(*a)&&
sizeof(int *)==sizeof(b)&&
sizeof(*b)==sizeof(**b);// all essentially int pointers,
                        // and would be equal

No, the standard does not require any of those to be true. The first is frequently false on 64-bit systems. The others are typically true, but if you're looking for guarantees then the standard does not offer them.

but if I have:

int *a;
float*b;  

registration and alignment are not guaranteed. i.e.:

Correct, the representations and alignment of float * and int * are not guaranteed to be the same.

The reason I ask is because of this discussion, where I posted an answer showing a function that creates a 3D array: [..] int ***arr; int x,y;

    arr = calloc(p, sizeof(arr)); 

That will often work as intended because on most systems, all object pointers in fact do have the same size, but C does not require it to be correct. It should be:

    arr = calloc(p, sizeof(*arr));

or

    arr = calloc(p, sizeof(int **));

Likewise, this:

        arr[x] = calloc(c ,sizeof(arr)); 

should be

    arr[x] = calloc(c ,sizeof(*arr[x])); 

or

    arr[x] = calloc(c ,sizeof(int *)); 

. This one is ok, though:

            arr[x][y] = calloc(r, sizeof(int));

For allocating memory, and as long as type stays int, in that statement, is there any danger of using any variation of int pointer (int *, int **, arr, *arr) as the argument to sizeof ?

Yes. Given any type T, T * is a different, incompatible type. Regardless of qualification or pointerness, the standard provides no guarantee that the two have the same representation or alignment. Part of not having a guarantee of the same representation is not having a guarantee of the same size. In particular, if T is int then there are common cases in which T and T * have different size.

Is one form preferred over the other? (please give reasons other than style)

Although it's arguable whether this is a point of style, this form:

arr = calloc(p, sizeof(*arr));

has the advantage that if you change the type of arr, you don't have to modify the calloc call. The correct size follows from the declaration of arr. It's also easy to tell that the size is right, without looking up the declaration of arr. And it's easy to write a macro around that form if you should wish to do so.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • isn't `sizeof(*arr)` dereferencing an unknown pointer (and potentially causing a seg fault)? – Russ Schultz Oct 01 '15 at 21:15
  • 2
    @RussSchultz - no, sizeof is evaluated at compile time according to the type of the element (which is known), as it doesn't really need the value, only the value size. – MByD Oct 01 '15 at 21:18
  • 1
    `int ***` is not an array. Why do so many people have this missconception about multi-star pointers, while understanding single star pointers? – too honest for this site Oct 01 '15 at 21:41
  • John - _a is a pointer to int. b is a pointer to int *. These are not compatible types_... This may be where I am interpreting the standard incorrectly. I assumed that if pointers are all created from the same type, then you would have compatibility between them, and if two pointers were created using two different types, then no compatibility. Thank you for this clarification. – ryyker Oct 01 '15 at 21:42
  • @ryyker: "_pointer to int_" is a type. "_pointer to pointer to int_" is a different, incompatible, type. – ninjalj Oct 01 '15 at 21:58
  • @ninjalj - Thank you.. I am starting to see this, I will have to do some more reading. – ryyker Oct 01 '15 at 22:01
  • 1
    @ryyker: If your assumption was true, ther would be only one pointer type and type checking between pointer types was useless/impossible. – too honest for this site Oct 01 '15 at 22:10
  • @Olaf, I don't see what the pointer/array issue has to do with my answer, but I'm right there with you. In fact I think the problem is not so much with pointers but with arrays, and I think too many people *don't* understand even 1D arrays. There is a rampant misconception that "array" is a synonym of "pointer", but of course they are completely different things. It probably doesn't help that Java is so popular, given that its multi-dimensional arrays actually are analogous to multi-star C pointers. – John Bollinger Oct 01 '15 at 22:10
  • @JohnBollinger: That was an addition, not a correction of your answer. You cited the corresponding part of the question and I just thought it would be good to clarify this. The last sentence was just a personal statement - there is at least one question per day asking about 2D or 3D array, while showing a pointer to pointer (...) or array of pointers. – too honest for this site Oct 01 '15 at 22:15
2

Consider a fantasy compiler that only makes pointers as wide as needed.

Code uses a large set (trillions) of int and only a small set (2) of int *.

int *ticket = malloc(sizeof *ticket * 1000ULL*1000*1000*1000);
int **purchase = malloc(sizeof *purchase * 2);

Pointer arithmetic need only work within the range + 1 of the allocated memory. Pointers to int need to have have precision of 39+ bits. Pointers to int * need only have 2+ bits. Such a compiler could maintain the base of all int in code, somewhere, and add the a scaled 39 bit pointer when needed to form the physical address. Pointers to int * could use their fews bits along with a hidden in code base and scale to produce the physical address of the int *.

This is a reason for casting to void * on printf("%p", (void *) ptr) as such a compiler would need to put together the base and scale to form a generic void * pointer which could point to any object in memory. It is also a reason why casting (int **)((void *)ticket) is UB.

Similar systems existed in DOS segment:offset days with the functions in one address space (being 32 or 16 bit) and data in another address space (being independent from code 32 or 16 bit).

Present data embedded processors sometimes use one pointer size for constant data and another size for variable data.

C supports many architectures, not only the flat model of a 64-bit pointer that can point anywhere.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • _Pointers to int need to have have precision of 39+ bits. Pointers to int * need only have 2+ bits_. Good illustration. – ryyker Oct 01 '15 at 21:54
  • @ryyker I should have used arrays to illustrate rather than `malloc()` as this fantasy compiler needs to know maximum usage before run time. Yet hopefully the idea is clear: different pointer types can be of different sizes. – chux - Reinstate Monica Oct 01 '15 at 21:59
  • Starting to understand. Be patient with me, I'll get it eventually. – ryyker Oct 01 '15 at 22:00
  • 1
    @ryyker What has helped me was assuming every different type existed in its own address space, each space of different sizes. All `int` lived at `Red:0` to `Red:999999` and `double` lived at `Blue:0` to `Blue:99` When cast to `void *`, some type specific address space prefix along with a scaled pointer value was put together to form the wide `void *`. Going from `void *`, back to the original is easy. The tricky bit is going from a `void *` generated for one type into _another_ type - it just does not work - hence UB. – chux - Reinstate Monica Oct 01 '15 at 22:13