Wednesday, January 2, 2013

Arrays and sizeof fun.

Sizeof arrays is a rather large land mine in c/c++. Even the most experienced coders will often hack out a piece of code and suddenly come to a screeching halt half way in when they realize they are being an idiot(hopeful they realize...).

The problem is that arrays in C are supposed to be well arrays.. but people often use them as shortcut to define some small non-standard chunk of data, and then you have a nice little surprise waiting to happen when your later upgrading or re-factoring or just using that piece of code. (otherwise know as the "30 seconds saved, 3 hours wasted" coding style..)

Here is what happens when you use arrays as a replacement type:
#include <iostream>

void func(char c[5])
{
  std::cout << "sizeof(param,5):" << sizeof(c) << std::endl;
}

void func2(char c[14])
{
  std::cout << "sizeof(param,14):" << sizeof(c) << std::endl;
}

typedef char Typedefed[5];

void func3(Typedefed c)
{
  std::cout << "sizeof(typedef,5):" << sizeof(c) << std::endl;
}

int main()
{
  char a[5];

  std::cout << "sizeof(local,5):" << sizeof(a) << std::endl;
  func(a);
  func2(a);
  func3(a);
}


Result (yes it does compile even with the char[14] array being handed a char[5], segv surprise.. yummy!):
sizeof(local,5):5
sizeof(param,5):4
sizeof(param,14):4
sizeof(typedef,5):4

So whats going on.. well this is the unfortunate consequence a design decision in the Ansi C standard "6.2.2.1 Lvalues and function designators".

"Except when it is the operand of the sizeof operator or the unary & operator, or is a character string literal used to initialize an array of character type, or is a wide string literal used to initialize an array with element type compatible with wchar_t, an lvalue that has type ``array of type'' is converted to an expression that has type ``pointer to type'' that points to the initial element of the array and is not an lvalue. "

So to work around it your forced to take other measures. Generally I believe its a good idea to do it the right way first time and simply create a trivial struct for your primitive type, consider it a slightly bloated version of a typedef. Your alternatives to this are always passing a ref to the array, or always passing its size with it.


#include <iostream>
void wasteful(char* c, std::size_t s)
{
  std::cout << "sizeof(c):" << sizeof(c) << std::endl;
  std::cout << "sizeof(s):" << sizeof(s) << std::endl;
  std::cout << "wasted  :" << sizeof(c) + sizeof(s) - s << std::endl;

  std::cout << "used  :" << s << std::endl;
}

void easytoforget(char (&e)[5])
{
  std::cout << "sizeof(e):" << sizeof(e) << std::endl;  
};

struct Structed
{
  Structed(char* a)  { memcpy(a_,a,sizeof(a_)); }

  char a_[5];
};

void fixed(Structed e)
{
  std::cout << "sizeof(e):" << sizeof(e) << std::endl;  
};

int main()
{
  char a[5];
 
  std::cout << std::endl << "Solutions:" << std::endl;
  wasteful(a, sizeof(a));
  easytoforget(a);
  fixed(a);
}