Array decay

From Adventure Game Studio | Wiki
Revision as of 03:56, 2 June 2013 by Monkey'05'06 (talk | contribs) (→‎Taking advantage: Fixing a typo in my totally legit code snippet.)
Jump to navigation Jump to search

Those familiar with the concept of array-to-pointer decay (or just array decay) may be surprised to find that AGS supports this, and in fact some games may rely quite heavily on it.

A brief description of array decay

Warning: The following is somewhat generalized in terms of AGScript and may not apply to "real" programming languages.

Array decay occurs when a fixed-size array is passed as a parameter to a function. The function can't actually take the array as a parameter, so instead it takes a pointer to the first item in the array. Thus, the array object is said to decay into a pointer.

Pointers to basic data types, in AGS?

Astonishing as it may sound, AGScript actually acknowledges the existence of int*s, char*s, and even float*s. Take a few moments to catch your breath after this revelation before continuing on...

AGScript doesn't allow pointers to basic data types in most situations because they can easily lead to unsafe code. This limitation does inhibit the practical usability of array decay in AGS, but don't get too far ahead of me -- the restriction is there for your protection. Pointers are a confusing enough concept for many beginning or novice programmers, and the implications of basic type pointers would only complicate matters further.

The practical usability of array decay in AGS

As AGS does not allow pointers to basic data types, there is currently only one way of utilizing array decay in AGS, thanks to the old-style string type.

A word on old-style strings

A quick glance at the AGS engine source code reveals that all of the functions that operate on old-style strings in AGS actually take a char* as their parameter. Internally, an old-style string is defined as a fixed-size char[200]. This array of 200 chars can decay into a char*, which is exactly what takes place when you call, for example, StrCopy:

string s; // defines char s[200];
StrCopy(s, "Hello World!"); // calls native C++ method like StrCopy(char*, const char*)

The above code snippet is actually completely synonymous in AGS to:

char s[200];
StrCopy(s, "Hello World!");

It will compile and run in exactly the same fashion, with exactly the same results.

Taking advantage

In reality, there are very few edge cases where one would be able to usefully take any advantage of this. Any actual benefit of implementing this as a workaround is highly likely to be outweighed by the disadvantages of implementation details. One example though may be generating a fixed-length string (of less than 200 characters).

String BufferToString(const string buffer)
{
  // the compiler will reject char[N]->String conversion
  // the conversion char[N]->char*->String (aka, char[N]->string->String) is valid though
  // this is array decay in action!
  return buffer;
}

#ifdef STRICT_STRINGS
import int StrContains(const string, const string); // just import this if new-style Strings enforced
#endif

String GenerateKey()
{
  char key[27]; // pad array by 1 for null terminating character
  char c[2]; // random character, plus null terminator
  int i = 0;
  while (i < 26)
  {
    c[0] = Random(25) + 65; // generate random character 'A'-'Z'
    if (StrContains(key, c) == -1)
    {
      buffer[i] = c[0];
      i++;
    }
  }
  return BufferToString(key); // create a new-style String via our helper method
}

Note that in this example we are dealing directly with two fixed-size char arrays, but we can still pass them into a function (StrContains) expecting string parameters. We are not using the standard 200 length arrays, which could have serious negative effects if misused. The point is that once the array decays into a pointer, its size is lost. However, this code would be reasonably less efficient if we were reallocating and copying the buffer with each new char, which is what happens when calling String.AppendChar.

The following old-style string methods modify the buffer and demand a fixed-size of 200 to prevent unsafe behavior:

  • StrCat
  • StrCopy
  • StrFormat
  • StrSetCharAt

The other old-style string methods do not modify the buffer and operate directly on the null-terminated string. If you utilize these methods, you are solely responsible for ensuring that the buffer is null-terminated to prevent unsafe behavior.

Wrap-up

So at the end of the day it's unlikely that there is a major performance boost hidden anywhere around here, but for the technophiles out there it may be interesting to know the underlying mechanics of AGS' old-style string. Perhaps in future versions the engine could expand and generalize this mechanic to allow a more meaningful usage. We'll see. -Monkey'05'06 (talk) 04:47, 2 June 2013 (BST)