Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is accessing a 2D array (T[N][M]) as a 1D array of size N*M guaranteed to work? [duplicate]

Can I be certain the following code will work?

int sum_array(int *array, size_t size)
{
  int i;
  int sum = 0;
  for (i=0;i<size;i++)
    sum += *(array+i);
  return sum;
}

int main()
{
  int two_d_array[4][3] = {{1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}};
  int sum = sum_array(&two_d_array[0][0], 12);
  printf("%d", sum);
}

While it makes sense for a 4-by-3 array to be laid out in memory exactly as a 12 element array, is it guaranteed? Since I seem to be cheating the type system, I am not so sure something could go wrong (for example, padding being added to int[3]).

Bonus points if you can elaborate on what happens if I use something other than ints in my array, and provide the relevant quote from the standard.

like image 914
FrancoVS Avatar asked Mar 26 '19 13:03

FrancoVS


1 Answers

for example, padding being added to int[3]

There's no danger of that. Arrays are guaranteed to not have padding. The memory layout itself is guaranteed, while the pointer arithmetic is technically undefined, as per this quote mentioned in the comments: expr.add/4 Thus:

Is 2D array (T[N][M]) traversal by pointer (T*) guaranteed to work?

Technically not according to my understanding of the standard, unfortunately.


Here is a version of standard std::accumulate that iterates the innermost elements of a multi dimensional array of any dimension count as if it were a flat array:

template<class T, unsigned size, class BinOp = std::plus<>>
auto arr_accumulate(
    T (&arr)[size], std::remove_all_extents_t<T> init = {}, BinOp op = {})
{
    if constexpr (std::is_array_v<T>) {
        return std::accumulate(std::begin(arr), std::end(arr), init,
            [&](const auto& l, const auto& r) {
                return arr_accumulate(r, l, op);
            }
        );
    } else {
        return std::accumulate(std::begin(arr), std::end(arr), init, op);
    }
}

// usage
int sum = arr_accumulate(two_d_array, 0);

I haven't benchmarked this to see if there's any overhead. With compile time constant input such as in your example, the sum was calculated at compile time.

Another implementation; only for trivial types:

template<class T, unsigned size>
constexpr auto flat_size (T (&arr)[size]) {
    using E = std::remove_all_extents_t<T>;
    return sizeof arr / sizeof (E);
}

template<class T, unsigned size, class BinOp = std::plus<>>
auto arr_accumulate_trivial(
    T (&arr)[size], std::remove_all_extents_t<T> init = {}, BinOp op = {})
{
    using E = std::remove_all_extents_t<T>;
    static_assert(std::is_trivially_copyable_v<E>);
    std::array<E, flat_size(arr)> flat;
    std::memcpy(flat.data(), arr, sizeof arr);
    return std::accumulate(std::begin(flat), std::end(flat), init, op);
}
like image 80
eerorika Avatar answered Oct 02 '22 03:10

eerorika