Treating a 2D Array as a 1D Array

Started by
7 comments, last by 21st Century Moose 12 years, 1 month ago
Alrighty folks, I've googled this, and I've found conflicting answers. I'm leaning towards this being undefined behavior, but I'm not sure. Say I have a 2D array:

int myArray[4][4];


And then there's a 3rd party function I need to use that takes a 1D array:

void doStuff(int* array); // assumes 16 elements in array


Is this well-defined or undefined by the standard:

doStuff(myArray[0]); // First method
doStuff(&(myArra[0][0])) // Second method



I know, the obvious answer is to go read the standard, and I've tried looking it up, but I'm not entirely sure what specific section it would be under, and the one I did check ended up confusing me by the time I finished the paragraph.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Advertisement
Arrays are guaranteed to be contiguous and, as a result, arrays of arrays are guaranteed to be contiguous as well. Thus, the array myArray[4][4] can be thought of as a contiguous chunk of 16 ints. If you pass in the address of the first element to a function that expects a pointer to 16 ints, then there won't be a problem, with the standard or otherwise.
You can also dereference your 2D array to make it 1D:

doStuff(*myArray);
Another way of handling this is with a good old-fashioned union:union _myArray
{
int arr2d[4][4];
int arr1d[16];
} myArray;

I actually prefer this approach as it would make what you're doing more explicit to anyone reading the code.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.


I know, the obvious answer is to go read the standard, and I've tried looking it up, but I'm not entirely sure what specific section it would be under, and the one I did check ended up confusing me by the time I finished the paragraph.

The relevant section would be 8.3.4 for all versions of the C++ standard.

[quote name='Cornstalks' timestamp='1331441179' post='4921046']
I know, the obvious answer is to go read the standard, and I've tried looking it up, but I'm not entirely sure what specific section it would be under, and the one I did check ended up confusing me by the time I finished the paragraph.

The relevant section would be 8.3.4 for all versions of the C++ standard.
[/quote]
Thanks, I had read 4.2.1, but didn't feel like that was the actual answer to the question. I think 8.3.4.8 & 9 really got into what I was after.



Arrays are guaranteed to be contiguous and, as a result, arrays of arrays are guaranteed to be contiguous as well. Thus, the array myArray[4][4] can be thought of as a contiguous chunk of 16 ints. If you pass in the address of the first element to a function that expects a pointer to 16 ints, then there won't be a problem, with the standard or otherwise.

Yes, that's what my intuition said, but I wasn't sure if there was a guarantee about how this memory was laid out, which this directly depends on. Turns out the standard states that arrays (and arrays of arrays) are stored row-wise, which guarantees the memory layout such that myArray[0][5] should be the same as myArray[1][1]. myArray[0][5] just looks soooo... wrong, knowing it's a 4x4 array. But hey, I guess it's valid.



You can also dereference your 2D array to make it 1D:

doStuff(*myArray);


Yeah, that's what I'm doing in the first method (though I've used array subscripting).



Another way of handling this is with a good old-fashioned union:union _myArray
{
int arr2d[4][4];
int arr1d[16];
} myArray;

I actually prefer this approach as it would make what you're doing more explicit to anyone reading the code.

Someone correct me if I'm wrong, but my understanding of section 9.5 of the standard ("Unions") states that "at most one of the data members can be active at any time," which, as I understand it, means that only the most recently set member has a well defined value (though people do this a lot without issues, but I think setting one member of a union and then reading another is technically undefined, not just in terms of what you get out, but in terms of if it'll even work). However, I'm wondering if this just might be valid in this case (section 9.5.1 talks about being able to access common members between PODs). I guess it depends on if arr2d and arr1d can be considered PODs with common members (16 ints, that is). Can anyone shed some light on this, and correct me if my first sentence is wrong?
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

View Postmhagain, on 11 March 2012 - 01:57 PM, said:
Another way of handling this is with a good old-fashioned union:

union _myArray
{
int arr2d[4][4];
int arr1d[16];
} myArray;

I actually prefer this approach as it would make what you're doing more explicit to anyone reading the code.
Someone correct me if I'm wrong, but my understanding of section 9.5 of the standard ("Unions") states that "at most one of the data members can be active at any time," which, as I understand it, means that only the most recently set member has a well defined value (though people do this a lot without issues, but I think setting one member of a union and then reading another is technically undefined, not just in terms of what you get out, but in terms of if it'll even work). However, I'm wondering if this just might be valid in this case (section 9.5.1 talks about being able to access common members between PODs). I guess it depends on if arr2d and arr1d can be considered PODs with common members (16 ints, that is). Can anyone shed some light on this, and correct me if my first sentence is wrong?


Unions are just an abstraction of the same memory space under different types. One of the things unions are useful for is, in fact, writing some memory location as some type, and then reading it with a different type, you just need to be careful with the sizes to match, the endianness of the machine and that things. Mhagain's solution is elegant and correct.

One of the things unions are useful for is, in fact, writing some memory location as some type, and then reading it with a different type, you just need to be careful with the sizes to match, the endianness of the machine and that things. Mhagain's solution is elegant and correct.

That's what I hear people say, but then the standard explicitly says they can only hold one active member at a time (in which case you'd be treating it as two here) and behavior is only defined for reading the active member, not the inactive members. While in practice this tends to work, I see no reason why the compiler couldn't stick an exception in there to be thrown when reading an inactive member. (read this and this if you don't want to dig through the standard on this one). Normally, I'd use a [font=courier new,courier,monospace]reinterpret_cast [/font]over a union if the goal was to write as one data type and read as a different data type.

However, the complication I see here is that the standard makes an exception for PODs in unions, and says that if one POD is the active member, but it shares some common initial sequence of members with an inactive POD member, then the common members of each may be accessed. An int is a POD, yes, and I want to believe that int [font=courier new,courier,monospace]arr2[4][4][/font] and[font=courier new,courier,monospace] int arr1[16][/font] could be considered as two PODs with the common initial member sequence of 16 ints. But I'd like someone to confirm this thinking for me before I potentially invoke undefined behavior.

Alternatively, what about:

union
{
float v[4];
struct
{
float x, y, z, w;
};
} myUnion;


Would I be correct in this also being a union of two PODs with a common initial member sequence of 4 floats?

If so, then yes, I'll probably use something like mhagain's solution, but I want to confirm this first.
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
My solution was actually inspired by Microsoft's D3DMATRIX type:
typedef struct _D3DMATRIX {
union {
struct {
float _11, _12, _13, _14;
float _21, _22, _23, _24;
float _31, _32, _33, _34;
float _41, _42, _43, _44;

};
float m[4][4];
};
} D3DMATRIX;

This is usable for C++ and is used on thousands of programs that ship each year, so one may assume that it works without issues. ;)

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

This topic is closed to new replies.

Advertisement