Okay, here I go :-)
Ways to deal with "arrays" in C
In C there are numerous ways to deal with array. For the remainder I will talk about string* (and use the variable strings which has a type of string*). This is because t[] "effectively decomposes" into t* and char* is the type of a "C string". Thus string* represents a pointer to "C string". This glosses over a number of pedantic issues in C w.r.t. "arrays" and "pointers". (Remember: just because a pointer can be accessed as p[i] doesn't make the type an array in C parlance.)
Now, strings (of type string*) has no way to know it's size -- it only represents a pointer to some string, or NULL perhaps. Now, let's look at some of the ways we can "know" the size:
Use a sentinel value. In this I am assuming the use NULL as the sentinel value (or it might be -1 for an "array" of integers, etc.). Remember that C has no such requirement that arrays have a sentinel value so this approach, like the following two, is just convention.
string* p;
for (p = strings; p != NULL; p++) {
doStuff(*p);
}
Track the array size externally.
void display(int count, string* strings) {
for (int i = 0; i < count; i++) {
doStuff(strings[i]);
}
}
Bundle the "array" and the length together.
struct mystrarray_t {
int size;
string* strings;
}
void display(struct mystrarray_t arr) {
for (int i = 0; i < arr.size i++) {
doStuff(arr.strings[i]);
}
}
Java uses this last approach.
Every array object in Java has a fixed sized which can be accessed as arr.length. There is special byte-code magic to make this work (arrays are very magical in Java), but at the language level this is exposed as just a read-only integer field that never changes (remember, each array object has a fixed size). Compilers and the JVM/JIT can take advantage of this fact to optimize the loop.
Unlike C, Java guarantees that trying to access an index out of bounds will result in an Exception (for performance reasons, even if it were not exposed, this would require the JVM kept track of the length of each array). In C this is just undefined behavior. For instance, if the sentinel value wasn't within the object (read "the desired accessibly memory") then example #1 would have lead to a buffer-overflow.
However, there is nothing to prevent one from using sentinel values in Java. Unlike the C form with a sentinel value, this is also safe from IndexOutOfBoundExceptions (IOOB) because the length-guard is the ultimate limit. The sentinel is just a break-early.
// So we can add up to 2 extra names later
String names[] = { "Fred", "Barney", null, null };
// This uses a sentinel *and* is free of an over-run or IOB Exception
for (String n : names) {
if (n == null) {
break;
}
doStuff(n);
}
Or possibly allowing an IOOB Exception because we do something silly like ignore the fact that arrays know their length: (See comments wrt "performance").
// -- THERE IS NO EFFECTIVE PERFORMANCE GAIN --
// Can ONLY add 1 more name since sentinel now required to
// cleanly detect termination condition.
// Unlike C the behavior is still well-defined, just ill-behaving.
String names[] = { "Fred", "Barney", null, null };
for (int i = 0;; i++) {
String n = strings[i];
if (n == null) {
break;
}
doStuff(n);
}
On the other hand, I would discourage the use of such primitive code -- better to just use a suitable data-type such as a List in almost all cases.
Happy coding.
()after it.struct myarray_t { int length; void* data; }