Jump to content
  • Advertisement
Sign in to follow this  
Cornstalks

Treating a 2D Array as a 1D Array

This topic is 2347 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

[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?

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
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. ;)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!