3

We can pass a 2d array as a single pointer and as well as a double pointer. But in the 2nd case the output is not as expected. So what is wrong in the 2nd code?

Method 1:

#include <stdio.h>
void print(int *arr, int m, int n)
{
    int i, j;
    for (i = 0; i < m; i++)
      for (j = 0; j < n; j++)
        printf("%d ", *((arr+i*n) + j));
}

int main()
{
    int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int m = 3, n = 3;
    print((int *)arr, m, n);
    return 0;
}

Output:

1 2 3 4 5 6 7 8 9

Method 2:

#include <stdio.h>
void print(int *arr[], int m, int n)
{
    int i, j;
    for (i = 0; i < m; i++)
      for (j = 0; j < n; j++)
        printf("%d ", *((arr+i*n) + j));
}

int main()
{
    int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int m = 3;
    int n = 3;
    print((int **)arr, m, n);
    return 0;
}

Output:

1 3 5 7 9 3 0 -1990071075 0
Pankaj Mahato
  • 1,051
  • 5
  • 14
  • 26
  • 1
    "We can pass a 2d array as a single pointer and as well as a double pointer" - you could pass it as a quadruple pointer to `char`. That doesn't mean its a good idea, or defined behavior to utilize (and it isn't, and its not). That hard-cast to `int**` and the failure of the code to compile without it should hint you something wicked this way comes. An `int**` is a pointer to pointer to `int` The *legal* conversion of `arr` to pointer as an expression is `int (*)[3]`. Those types are *not equivalent*. Arrays are not pointers, regardless of what you may have been led to believe. – WhozCraig Feb 24 '15 at 09:01
  • 1
    A rule of thumb in C is to avoid 2D arrays. Just use a 1D array and access it as `a[i*width+j]` ... – Basile Starynkevitch Feb 24 '15 at 09:30
  • But @BasileStarynkevitch in my project I have to pass 2D array to function so I can't avoid 2D array – Pankaj Mahato Feb 24 '15 at 09:32
  • You can *always* avoid 2D arrays: they don't exist in the hardware, and C is (or was) a language designed to be close to the hardware. – Basile Starynkevitch Feb 24 '15 at 09:38
  • @BasileStarynkevitch can you give me a simple example? – Pankaj Mahato Feb 24 '15 at 10:00
  • 1
    I gave it already in my first comment. – Basile Starynkevitch Feb 24 '15 at 10:01

2 Answers2

5

The first one is undefined behavior: Accesing a 2D array using a single pointer.

The second one is simply wrong, you can't pass a 2D array (arr[][3]) to an array of int pointers (*arr[]), take a look to Correct way of passing 2 dimensional array into a function:

void print(int *arr[], int m, int n)

Must be

void print(int arr[][3], int n) /* You don't need the last dimesion */

or

void print(int (*arr)[3], int n) /* A pointer to an array of integers */

But this way the column in arr[][3] must be globally defined. Isn't any other workaround?

Under C99 you can use VLA's (Variable-length array):

void print(int rows, int cols, int arr[rows][cols])
Community
  • 1
  • 1
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • 2
    Correct me if i'm wrong, but the way i see it, he treats a matrix as a linearized array (calculating indexes) without actual indexation, which i belive is perfectly legal (frist case). – HighPredator Feb 24 '15 at 08:46
  • But this way the column in arr[][3] must be globally defined. Isn't any other workaround? – Pankaj Mahato Feb 24 '15 at 08:46
  • 2
    @PankajMahato, under C99 you can use [VLA's](http://en.wikipedia.org/wiki/Variable-length_array) – David Ranieri Feb 24 '15 at 08:48
  • @HighPredator It's not legal according to the standard, but you can get away with it most of the times. – Marek Vavrusa Feb 24 '15 at 08:57
  • @MarekVavrusa, that's what I'm trying to figure out. How come it can be UB if for example i calculated a pointer address correctly, the element is there and I'm not outside of the memory block? Btw. can you link the standard paragraph, so I wouldn't be asking more of seemingly stupid questions? – HighPredator Feb 24 '15 at 09:01
  • @HighPredator It's already linked in the Alter Mann's answer (first paragraph). – Marek Vavrusa Feb 24 '15 at 09:09
  • 1
    The first one is correct; you misunderstood the article you linked to. `(int *)arr` means we are iterating over all of `arr`; the other question features `&a[0][0]` which means we are only iterating over `a[0]` – M.M Feb 24 '15 at 09:13
  • @PankajMahato yes, you can always "flatten" a true array (so long as you're careful to get the pointer from `arr` or `&arr`, rather than from a sub-array as in the article linked by Alter Mann) – M.M Feb 24 '15 at 09:17
  • 1
    @PankajMahato No it isn't read the first paragraph in the linked standard. You can get away with it, but you shouldn't use it like this. – Marek Vavrusa Feb 24 '15 at 09:19
  • 1
    @MattMcNabb, `If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.` – David Ranieri Feb 24 '15 at 09:20
  • @AlterMann, now I'm totally lost... Regarding the case 1, it's UB because? Because the resulting pointer type is wrong or what? – HighPredator Feb 24 '15 at 09:34
  • @HighPredator, because you can't treat an `int arr[3][3]` as an `int arr[9]`, have you read [this answer](http://stackoverflow.com/a/7787436/1606345)?, Christoph explains it very well. – David Ranieri Feb 24 '15 at 09:38
  • @AlterMann the example you liked is only UB because of `&foo[0][0]`; it would be OK if that was replaced with `(int *)foo` – M.M Feb 24 '15 at 09:41
  • @MattMcNabb, again, accessing one past the last element of the array is UB, OP uses a 1D array, thus accessing `arr[1][0]` is UB, if you do not agree with this tell it to the standard. – David Ranieri Feb 24 '15 at 09:46
  • @AlterMann If we point to `arr` then "the array object" is `arr` (not `arr[0]`). Also, you say "OP is passing a 1D array" but arrays cannot be passed by value in C. OP is passing a pointer which is bounded to all of `arr` (not a suboject of arr). The wording in your bolded quote is imprecise because it does not clearly say *which* array object when there are nested arrays, however in accordance with the rationale of allowing bounds-checked pointers, a pointer should be bounded according to the object it was pointed to (not some subobject of that) – M.M Feb 24 '15 at 09:51
  • before anyone says "`but (int *)arr` is not pointing to an element of `arr`", think about `char *p = (char *)&arr[0][0]; p += 2;`. Does that cause UB? After all, neither `p` nor `p + 2` point to any elements of `arr` or its sub-arrays. – M.M Feb 24 '15 at 10:03
  • Matt, it is defined as such (UB), because type `int *arr[]` ambiguously represents an array of `int*`, in which case treating it as a contiguous block of memory would fail, or an array of arrays in which it usually works. Usually because the compiler *may* insert padding between the sub-arrays for aliasing. Your argument that all compilers must generate multi-dimensional arrays in contiguous memory on the basis that your compiler does it is false, the standard says they don't have to. – Marek Vavrusa Feb 24 '15 at 10:13
  • @MarekVavrusa, hold on a second. What you're saying goes against the answer from the link. Specifically: "C arrays - even multi-dimensional ones - are contiguous, ie an array of type int [4][5] is structurally equivalent to an array of type int [20]." – HighPredator Feb 24 '15 at 10:15
  • @MarekVavrusa, well ... as far as I know multidimesional arrays (`int arr[][3]`) must be in contiguous memory. – David Ranieri Feb 24 '15 at 10:16
  • @MarekVavrusa, in other words, `sizeof(int arr[3][3])` = `sizeof(int arr[3] * 3)` there can't be padding bytes. – David Ranieri Feb 24 '15 at 10:28
  • 1
    @All, I think i found the proper explanation I was looking for. http://stackoverflow.com/a/25140160/3651664 – HighPredator Feb 24 '15 at 10:28
  • @AlterMann, thx for pointing that fact out in the first place. Sorry if I bothered you too much, but that stuff really bugged me out:) – HighPredator Feb 24 '15 at 10:37
  • 1
    @AlterMann @HighPredator I think my wording was poor. The "standard doesn't say" is not equal to "standard says not". I admit, the array subscription in C / pointer aliasing is shady. Sure that `int [4][5]` and `int [4*5]` may have a same size, and therefore may be considered as equal, but they're not. My understanding of the standard in this is case is that out-of-bounds access (this cover "linearization" in the OP's question for anything other than `char`) is not defined not only because the C originally wanted to do bounds checking, but also to comply with aligned members. – Marek Vavrusa Feb 24 '15 at 11:04
  • 1
    @HighPredator note the distinction between `a[1][7]` which goes out of bounds of `a[1]`, and `((int *)a)[12]` which is OK because we were never anything to do with `a[1]` or any other sub-array of `a`.; it's still in the bounds of `a` – M.M Feb 24 '15 at 20:06
1

Alter Mann is right, but the main problem in Method 2 is this code:

*((arr+i*n) + j)

Since arr is now type of int *arr[], the element size is sizeof(int *) and not sizeof(int) as in the first case. So when f.e. arr = 0, then arr + 1 equals to 0 + sizeof(int*) instead of 0 + sizeof(int) as in the first case. It would work okay if the arr was casted to (int*) like:

*(((int *)arr+i*n) + j)

TL;DR you're jumping through the array by the pointer size, not an integer size.

My personal suggestion is to use pointers, but access it like an array:

int *arr[];
return arr[i][j];

This works every time unlike the pointer arithmetic that might bite you with the step size, like it did with your case.

Marek Vavrusa
  • 246
  • 1
  • 7
  • Just to make things a bit clearer: it's UB because the result of pointer type (from the OP's example) after pointer arithmetic differs from int*, thus, a cast is required? – HighPredator Feb 24 '15 at 09:10
  • It's not UB, it's just reading out-of-bounds. On X86_64, the program reads memory on positions `[0, 8, 16, 24, 32 ...]` instead of `[0, 4, 8, ...]` because the step size is different. To illustrate this, try printing: `(int*)NULL + 1` and `(int)0 + 1` and see how it differs. – Marek Vavrusa Feb 24 '15 at 09:16
  • There's nothing wrong with `*((arr+i*n) + j)` in Method 1 (other than it is harder to read than `arr[i*n+j]` . Are you talking about Method 2 or what? – M.M Feb 24 '15 at 09:17
  • 1
    There is, as with my response to Alter Mann's answer (standard). I'm showing why the Method 2 is wrong. – Marek Vavrusa Feb 24 '15 at 09:18
  • OK, I'd suggest editing your answer to say that you are talking about Method 2 – M.M Feb 24 '15 at 09:38