11
void compute(int rows, int columns, double *data) {
    double (*data2D)[columns] = (double (*)[columns]) data;
    // do something with data2D
}

int main(void) {
    double data[25] = {0};
    compute(5, 5, data);
}

Sometimes, it'd be very convenient to treat a parameter as a multi-dimensional array, but it needs to be declared as a pointer into a flat array. Is it safe to cast the pointer to treat it as a multidimensional array, as compute does in the above example? I'm pretty sure the memory layout is guaranteed to work correctly, but I don't know if the standard allows pointers to be cast this way.

Does this break any strict aliasing rules? What about the rules for pointer arithmetic; since the data "isn't actually" a double[5][5], are we allowed to perform pointer arithmetic and indexing on data2D, or does it violate the requirement that pointer arithmetic not stray past the bounds of an appropriate array? Is data2D even guaranteed to point to the right place, or is it just guaranteed that we can cast it back and recover data? Standard quotes would be much appreciated.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • This is a VLA type, which makes it C-specific and perhaps a bit trickier, but the basic idea is that arrays are nothing more than a stored sequence of elements. Aliasing rules don't apply. It should work fine. – Potatoswatter Feb 28 '14 at 11:27
  • @Potatoswatter: Do you have a reference for how array types interact with strict aliasing? – user2357112 Feb 28 '14 at 11:35
  • I'm hoping to leave the entire question to someone else. You can look up strict aliasing in the C11 standard, which you can download as "N1570.pdf", §6.5/7. The tricky part is what constitutes an "access." This stuff is really pretty hairy. – Potatoswatter Feb 28 '14 at 11:40
  • It's important to note that the reverse casting doesn't work, i.e., `char *data; char data2D[4][8]; data = (char (*)[8])data2D;` won't work. – ajay Feb 28 '14 at 13:38
  • @ajay: That's because you're using the wrong cast. You need to cast to `char*`, not `char (*)[8]`. – user2357112 Feb 28 '14 at 14:20
  • @user2357112 Sorry, I had meant to say `char **data`. – ajay Feb 28 '14 at 20:28
  • @Potatoswatter There is nothing in the C standard called "strict aliasing". It is a term made up by compiler people. Though the section of the standard you refer to is the correct one. – Lundin Mar 03 '14 at 10:09
  • @Lundin Well, I don't mind being called a "compiler person." You are right though, and I gather that it was invented as a bit of a disparagement to the standard. – Potatoswatter Mar 03 '14 at 10:31

2 Answers2

1

I apologize in advance for a somewhat vague answer, as someone said these rules in the standard are quite hard to interpret.

C11 6.3.2.3 says

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.

So the actual cast is fine, as long as both pointers have the same alignment.

And then regarding accessing the actual data through the pointer, C11 6.5 gives you a wall of gibberish text regarding "aliasing", which is quite hard to understand. I'll try to cite what I believe are the only relevant parts for this specific case:

"The effective type of an object for an access to its stored value is the declared type of the object, if any." /--/

"An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

  • a type compatible with the effective type of the object, "

/--/

  • "an aggregate or union type that includes one of the aforementioned types among its members"

(The above is sometimes referred to as the "strict aliasing rule", which isn't a formal C language term, but rather a term made up by compiler implementers.)

In this case, the effective type of the object is an array of 25 doubles. You are attempting to cast it to an array pointer to an array of 5 doubles. Whether it counts as a type compatible with the effective type, or as an aggregate which includes the type, I'm not sure. But I'm quite sure it counts as either of those two valid cases.

So as far as I can see, this code doesn't violate 6.3.2.3 nor 6.5. I believe that the code is guaranteed to work fine and the behavior should be well-defined.

Community
  • 1
  • 1
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • This helps. It doesn't completely answer the question, but it helps. Though now I'm wondering why accessing the elements of an array doesn't violate those rules. I don't think an array type is compatible with the type of its elements, and the element type isn't an aggregate or union type including the array type among its members, but indexing lets us access the stored value of an array through an lvalue expression of the member type. I think the resolution has something to do with what counts as a "stored value". – user2357112 Mar 03 '14 at 12:52
0

The safest way in such situations is to keep the elements in a flat 1-dimensional array, but write accessor methods to read and write from this array in a multi dimensional way.

#include <stdio.h>
#include <string.h>

const int rowCount = 10;
const int columnCount = 10;

const int dataSize = rowCount*columnCount;
double data[dataSize];


void setValue( const int x, const int y, double value)
{
    if ( x>=0 && x<columnCount && y>=0 && y<rowCount) {
        data[x+y*columnCount] = value;
    }
}


double getValue( const int x, const int y )
{
    if ( x>=0 && x<columnCount && y>=0 && y<rowCount) {
        return data[x+y*columnCount];
    } else {
        return 0.0;
    }
}


int main()
{
    memset(data, 0, sizeof(double)*dataSize);
    // set a value
    setValue(5, 2, 12.0);
    // get a value
    double value = getValue(2, 7);

    return 0;
}

The example uses global variables which are only used for simplicity. You can either pass the data array as additional parameter to the functions, or even create a context to work with.

In c++ you would wrap the data container into a class and use the two methods as access methods.

Flovdis
  • 2,945
  • 26
  • 49
  • 1
    While this is safe, it doesn't answer the question. I've seen helper functions and macros for doing this. They're never as clean as real multidimensional indexing. It's one of the reasons VLAs were added to the language. – user2357112 Mar 03 '14 at 09:10