8

if I were to say

int (*i)[10] = malloc(size(int *) * 5);

this would allocated memory that looks like

{ (int *) , (int *) , (int *) , (int *) , (int *) }

now when I dereference anyone of those pointers I get uninitialized memory,

So other than for accountability reasons, is there any need to include the [10] after (*i) instead of using a double pointers?

Does using the 10 actually allocate space for ten ints, because if it did we wouldn't be able to access it?

rubixibuc
  • 7,111
  • 18
  • 59
  • 98

2 Answers2

10

Apologia

There is some confusion, probably on my part; for that, I apologize. From somewhere, presumably the answer by x4u, I copied the notation:

int (*arr)[10] = malloc(sizeof(*arr) * 5);

My main answer, immediately following, addresses this C statement. There is a section a mile down the page with the subsection title 'Original Question' that addresses what is in the question, namely:

int (*i)[10] = malloc(size(int *) * 5);

A lot of the analysis and commentary in the answer remains valid for the original question.


Answer

Consider the C statement:

int (*arr)[10] = malloc(sizeof(*arr) * 5);

The type of arr is 'pointer to array of 10 int'. Therefore, the value of sizeof(*arr) is 10 * sizeof(int). Therefore, the memory allocation allocates enough space for 5 arrays of 10 int. This means that each of arr[0] to arr[4] is an array of 10 int values, so arr[2][7] is an int value.

How to demonstrate this? Some code, I suppose using C99 printf() formats. It compiles cleanly and runs cleanly under valgrind.

Example code: pa.c

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>

int main(void)
{
    int (*arr)[10] = malloc(sizeof(*arr) * 5);

    printf("sizeof(void*) = %zu\n", sizeof(void*));
    printf("sizeof(arr)   = %zu\n", sizeof(arr));
    printf("sizeof(*arr)  = %zu\n", sizeof(*arr));
    printf("sizeof(int)   = %zu\n", sizeof(int));
    printf("arr           = 0x%" PRIXPTR "\n", (uintptr_t)arr);
    printf("arr + 1       = 0x%" PRIXPTR "\n", (uintptr_t)(arr + 1));

    putchar('\n');

    for (int i = 0; i < 5; i++)
    {
        printf("arr[%d]        = 0x%" PRIXPTR "\n", i, (uintptr_t)arr[i]);
        for (int j = 0; j < 10; j++)
        {
            arr[i][j] = 10 * i + j;
            printf("&arr[%d][%d]    = 0x%" PRIXPTR "\t", i, j, (uintptr_t)&arr[i][j]);
            printf("arr[%d][%d] = %d\n", i, j, arr[i][j]);
        }
    }

    free(arr);

    return 0;
}

Compilation and trace

$ gcc -O3 -g -std=c99 -Wall -Wextra -o pa pa.c
$ valgrind pa
==28268== Memcheck, a memory error detector
==28268== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==28268== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==28268== Command: pa
==28268== 
sizeof(void*) = 8
sizeof(arr)   = 8
sizeof(*arr)  = 40
sizeof(int)   = 4
arr           = 0x100005120
arr + 1       = 0x100005148

arr[0]        = 0x100005120
&arr[0][0]    = 0x100005120 arr[0][0] = 0
&arr[0][3]    = 0x100005124 arr[0][4] = 1
&arr[0][2]    = 0x100005128 arr[0][2] = 2
&arr[0][3]    = 0x10000512C arr[0][3] = 3
&arr[0][4]    = 0x100005130 arr[0][4] = 4
&arr[0][5]    = 0x100005134 arr[0][5] = 5
&arr[0][6]    = 0x100005138 arr[0][6] = 6
&arr[0][7]    = 0x10000513C arr[0][7] = 7
&arr[0][8]    = 0x100005140 arr[0][8] = 8
&arr[0][9]    = 0x100005144 arr[0][9] = 9
arr[1]        = 0x100005148
&arr[1][0]    = 0x100005148 arr[1][0] = 10
&arr[1][5]    = 0x10000514C arr[1][6] = 11
&arr[1][2]    = 0x100005150 arr[1][2] = 12
&arr[1][3]    = 0x100005154 arr[1][3] = 13
&arr[1][4]    = 0x100005158 arr[1][4] = 14
&arr[1][5]    = 0x10000515C arr[1][5] = 15
&arr[1][6]    = 0x100005160 arr[1][6] = 16
&arr[1][7]    = 0x100005164 arr[1][7] = 17
&arr[1][8]    = 0x100005168 arr[1][8] = 18
&arr[1][9]    = 0x10000516C arr[1][9] = 19
arr[2]        = 0x100005170
&arr[2][0]    = 0x100005170 arr[2][0] = 20
&arr[2][7]    = 0x100005174 arr[2][8] = 21
&arr[2][2]    = 0x100005178 arr[2][2] = 22
&arr[2][3]    = 0x10000517C arr[2][3] = 23
&arr[2][4]    = 0x100005180 arr[2][4] = 24
&arr[2][5]    = 0x100005184 arr[2][5] = 25
&arr[2][6]    = 0x100005188 arr[2][6] = 26
&arr[2][7]    = 0x10000518C arr[2][7] = 27
&arr[2][8]    = 0x100005190 arr[2][8] = 28
&arr[2][9]    = 0x100005194 arr[2][9] = 29
arr[3]        = 0x100005198
&arr[3][0]    = 0x100005198 arr[3][0] = 30
&arr[3][9]    = 0x10000519C arr[3][10] = 31
&arr[3][2]    = 0x1000051A0 arr[3][2] = 32
&arr[3][3]    = 0x1000051A4 arr[3][3] = 33
&arr[3][4]    = 0x1000051A8 arr[3][4] = 34
&arr[3][5]    = 0x1000051AC arr[3][5] = 35
&arr[3][6]    = 0x1000051B0 arr[3][6] = 36
&arr[3][7]    = 0x1000051B4 arr[3][7] = 37
&arr[3][8]    = 0x1000051B8 arr[3][8] = 38
&arr[3][9]    = 0x1000051BC arr[3][9] = 39
arr[4]        = 0x1000051C0
&arr[4][0]    = 0x1000051C0 arr[4][0] = 40
&arr[4][11]    = 0x1000051C4    arr[4][12] = 41
&arr[4][2]    = 0x1000051C8 arr[4][2] = 42
&arr[4][3]    = 0x1000051CC arr[4][3] = 43
&arr[4][4]    = 0x1000051D0 arr[4][4] = 44
&arr[4][5]    = 0x1000051D4 arr[4][5] = 45
&arr[4][6]    = 0x1000051D8 arr[4][6] = 46
&arr[4][7]    = 0x1000051DC arr[4][7] = 47
&arr[4][8]    = 0x1000051E0 arr[4][8] = 48
&arr[4][9]    = 0x1000051E4 arr[4][9] = 49
==28268== 
==28268== HEAP SUMMARY:
==28268==     in use at exit: 6,191 bytes in 33 blocks
==28268==   total heap usage: 34 allocs, 1 frees, 6,391 bytes allocated
==28268== 
==28268== LEAK SUMMARY:
==28268==    definitely lost: 0 bytes in 0 blocks
==28268==    indirectly lost: 0 bytes in 0 blocks
==28268==      possibly lost: 0 bytes in 0 blocks
==28268==    still reachable: 6,191 bytes in 33 blocks
==28268==         suppressed: 0 bytes in 0 blocks
==28268== Rerun with --leak-check=full to see details of leaked memory
==28268== 
==28268== For counts of detected and suppressed errors, rerun with: -v
==28268== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)
$

Testing on MacOS X 10.7.2 with GCC 4.6.1 and Valgrind 3.7.0.


Original question

The actual question, it seems, was about the allocation:

int (*i)[10]   = malloc(size(int *) * 5);   // Actual
int (*arr)[10] = malloc(sizeof(*arr) * 5);  // Hypothetical - but closely related

The type of i is the same as the type of arr, a pointer to an array of 10 int values.

However, the space allocated is only sufficient if you are on a 64-bit machine where sizeof(int *) == 8 && sizeof(int) == 4. Then you have (coincidentally) allocated enough space for one array.

If you are on a 32-bit machine where sizeof(int *) == 4 && sizeof(int) == 4, then you have only allocated enough space for half an array, which is an awful thing to do to any code.

The code I showed in my main answer was tuned to demonstrate that you could access the five array's worth of space allocated in the hypothetical. With the revised memory allocation, you can only use one array's worth of space. With that change, the rest of my commentary applies unchanged.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • I see what u are saying, so when I have a array such as int (*array)[10] I calculate a position like *(array + i) + j, but for a double pointer I would use *(*(array + i) + j) to access position array[i][j]? The difference being how I would access the second dimension? – rubixibuc Jan 20 '12 at 17:43
  • Sort of...`int (*arrayptr)[10]` is a pointer to an array of ten integers. Just like a `char *` can point to an individual character or to the start of an array of characters, so too a pointer to an array of integers can point to an individual array of integers or to the start of an array of arrays of 10 integers. Your allocation allocates multiple arrays. Therefore `arr` (in the question) can be indexed `arr[1]` to access the second array. And to access an element within the array, you subscript it: `arr[1][4]`. And yes, that is equivalent to `*(*(arr + 1) + 4)`. – Jonathan Leffler Jan 20 '12 at 18:11
  • Another question I have is how come *(arr + 1) and (arr + 1) print the same thing? – rubixibuc Jan 20 '12 at 18:18
  • Because the address of the array is the same as the address of the first (zeroth) element of the array, but the type of `*(arr + 1)` is different from the type of `(arr + 1)`, and so are the sizes. – Jonathan Leffler Jan 20 '12 at 18:54
  • How come when I use the * operator it normally takes and address and gives me the value at that address but here it just changes the type? And when does it do one or the other? – rubixibuc Jan 20 '12 at 19:00
  • Shouldn't arr be the address of the first element and *arr be the value of the element? – rubixibuc Jan 20 '12 at 19:09
  • Pointers to arrays crop up on StackOverflow and not in 'real life' (mercifully). At least, within very broad limits, that's my experience over the last 25+ years. They're confusing. They're tricky. It used to be that some compilers got it wrong (25+ years ago). I'll try and address your questions later with some diagrams; however, for the next N hours, I need to at least pretend to get some work done on my normal job (which doesn't involve working with pointers to arrays for essentially all of the N million lines of C code I work on). – Jonathan Leffler Jan 20 '12 at 19:28
  • Technically in the question @rubixibuc uses the statement "int (*i)[10] = malloc(size(int *) * 5);" which is different than you use "int (*arr)[10] = malloc(sizeof(*arr) * 5);". Meaning he was correct, he was only allocating an array of "int *" and using his statement in your code would be trouble. The answer is still good, because if you wanted to do something like this, you should use your allocation statement (else you are under allocating and memory corruption issues ensue). – Jon L Jan 20 '12 at 19:49
  • @JonL: I'm puzzled - I copied and pasted the quote, and you can see that x4u also has the same quote I have; but the question appears to have changed. This sort of thing is confusing for those who answer early versions of the question, and for those who come later. Presumably, rubixibuc modified the question within the first 5 minutes of asking it. – Jonathan Leffler Jan 20 '12 at 21:20
  • @Jonathan: Actually the question was never revised so far. It originally asked why `int (*i)[10] = malloc(size(int *) * 5)` leads to uninitialized memory when it is derefernced and I answered that `int (*arr)[10] = malloc(sizeof(*arr) * 5);` will fix it. So if you copied it from somewhere you probably got it from my answer. Apart from that your answer shows nicely that my suggested fix obviously is correct which makes me wonder why you said in one of your former edits that I was wrong. – x4u Jan 20 '12 at 21:52
  • @x4u: if I copied from your answer instead of the question, then ... well, I guess I'm getting careless. **For that, I apologize.** I have altered my answer somewhat to address that mistake on my part. I still hold that `int (*)[10]` is not a 'rectangular 2-dimensional array of `int` with inner length 10' as your answer still says; it is a pointer to an array of 10 `int` values. That part of your answer is still wrong, IMNSHO. – Jonathan Leffler Jan 20 '12 at 22:23
  • @Jonathan: No problem and I rephrased my answer now. What I had written this morning was only to explain why it needs to be a malloc of `sizeof(int[5][10])` to avoid the uninitialized memory access. Unfortunatly I didn't have more time today to chnage it earlier. – x4u Jan 20 '12 at 23:02
  • @x4u: I know the 'no time' problem...and the update is a definite improvement. – Jonathan Leffler Jan 20 '12 at 23:42
5

You need to calculate the entire size of the array when you allocate it:

int (*arr)[10] = malloc(sizeof(*arr) * 5);

The type int(*)[10] is a pointer to int[10] and as such can refer to the first element of a rectangular 2-dimensional int array with inner length 10 and unspecified outer length. To allocate memory for this type you need to allocate the entire 2 dimensinal array in a single block that has the size of int[5][10]. sizeof(*arr) is the same as sizeof(int[10]) which is the size of a single inner element and calculates to 40 for 32 bit int types.

x4u
  • 13,877
  • 6
  • 48
  • 58
  • 2
    I'm not sure this is correct. `arr[10]` is **not** of type `int *`, but rather of type `int[10]`. Have you tested the code posted? – user541686 Jan 20 '12 at 05:21
  • ur right it doens't compile for that reason, how would I allocated the memory then? – rubixibuc Jan 20 '12 at 05:27
  • sorry, fixed this, now it should compile. – x4u Jan 20 '12 at 05:28
  • @x4u, still doesn't compile, there error I think is because you can't assign a pointer to an array type, but the raised the question of how would u assign it? Would u have to type cast? – rubixibuc Jan 20 '12 at 05:32
  • @Mehrdad, is there any point to initializing memory in this way, or is it not needed? – rubixibuc Jan 20 '12 at 05:35
  • sorry, I wrote some nonsense. I'm going to fix it. – x4u Jan 20 '12 at 05:40
  • it's all good, I've done that more than enough times myself :-) – rubixibuc Jan 20 '12 at 05:41
  • @x4u what does sizeof(*arr) mean? – rubixibuc Jan 20 '12 at 05:44
  • 1
    @rubixibuc: I don't know why you're working with `int *` at all. You're allocating an `int[10]` (which is a block of 10 elements) and obtaining a pointer to *the entire block* (as opposed to a pointer to a single one of the elements), not allocating a block of 10 pointers. But the base address of the entire block is the same as that of the first element, and the contents of the block are the combined contents of all the elements. There really isn't much of a point in allocating a pointer to an array, it just causes confusion. – user541686 Jan 20 '12 at 05:44
  • @x4u, I think I get it now, so when you do it like this it must be allocated similarly to how automatic or static arrays are set up internally, so it is one giant block, with int[10] segments? – rubixibuc Jan 20 '12 at 05:50
  • @Mehrdad: yes you are right I misread the type declaration and thus my entire answer was wrong. I changed it now. – x4u Jan 20 '12 at 05:51
  • @rubixibuc: yes, `int (*arr)[10]` is the pointer type to which an `int arr[][10] = {...}` decays. – x4u Jan 20 '12 at 05:54
  • @x4u is my answer right? I'm not sure if u mean Mehrdad or me. Also thank you for the help :-) – rubixibuc Jan 20 '12 at 05:55
  • You say: _The type `int(*)[10]` is a rectangular 2-dimensional int array with inner length 10_; this is wrong. It (`arr`) is a pointer to an array of 10 `int` values. – Jonathan Leffler Jan 20 '12 at 06:34
  • @Jonathan Leffler: Yes, it is a pointer to `int[10]` and as such it can be used as a rectangular 2-dimensional int array in contrast to an array of `int *` which could form a jagged array. – x4u Jan 20 '12 at 07:26