0

For a single (string) array, the end is denoted by \0. For example, if I create the string:

char * string = "hello";

It will be represented as {'h','e','l','l','o','\0'}.

What is done for an array such as the following to denote "its end" ?

char * strings[] = {"one", "two"};

When iterating through a loop, how would I detect 'the end' of the array?

FI-Info
  • 635
  • 7
  • 16

2 Answers2

3

You could do something like this, which is nicely analogous to null termination for strings:

char * strings[] = {
    "one",
    "two",
    NULL
};

You could print those strings using a loop like this:

char **pp;
for(pp = strings; pp != NULL; pp++)
    printf("%s\n", *pp);

But when I say "you could do", I mean it. There's no rule saying you must do it this way. (This is in contrast to the strings themselves, where you really must use null-character termination if you construct your own strings and if you want yours to be compatible with strcpy, printf, etc.)

The NULL termination I have shown works well for an array of pointers. It can be harder to pick an appropriate sentinel value for other types, such as array of int. (If you know your numbers are all positive, you can use 0 or -1 as a sentinel, but if your array has both positive and negative numbers, you may be stuck.)

The other possibility is to maintain a separate count:

char * strings[] = {
    "one",
    "two"
};

int nstrings = 2;


int i;
for(i = 0; i < nstrings; i++)
    printf("%s\n", strings[i]);

Rather than counting the strings by hand and explicitly initializing nstrings to 2, the better technique is to let the compiler do it for you:

int nstrings = sizeof(strings) / sizeof(strings[0];

sizeof(strings) gives you the size of the whole array in bytes, while sizeof(strings[0]) gives you the size of one element -- that is, one pointer -- in bytes, and the division therefore gives you the number of entries, which is what you want.

Using sizeof and dividing like that is a pefectly common and perfectly legal idiom, but it's mildly obscure, so I prefer to encapsulate it (and make it a bit more self-documenting) with a macro, like this:

#define SIZEOFARRAY(a) (sizeof(a) / sizeof(a[0])

int nstrings = SIZEOFARRAY(strings);
Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • Although "while sizeof(strings[0]) gives you the size of one pointer in bytes" is correct in this case, "while sizeof(strings[0]) gives you the size of one array element in bytes" is also correct and more general purpose. – chux - Reinstate Monica Sep 11 '19 at 23:20
  • 1
    @chux It's a tradeoff. I like to take these things one small, explicit step at a time, since diving in with too many levels of abstraction too soon (like saying "one element") can be confusing. I deliberately said "one pointer" because here we know we're talking about an array of pointers. But then again, it's way too easy to get confused about arrays and pointers, so there's yet another possible misreading there. Compromise wording adopted. – Steve Summit Sep 11 '19 at 23:27
3

The array has compile-time constant size, and the number of elements can be determined by the expression sizeof(strings) / sizeof(*strings). Given then:

const size_t STRING_COUNT = sizeof(strings) / sizeof(*strings) ;

Then the last element in the array is:

strings[STRING_COUNT - 1] ;

and the array can be iterated with:

for( size_t i = 0; i < STRING_COUNT; i++ ) ...

If strings is passed as an argument, it will be passed as a pointer, so the array size information will be lost. In that case you would normally pass the size information as a separate argument:

some_function( strings, STRING_COUNT ) ;

Alternatively you can explicitly add a sentinel value to the array:

char* strings[] = 
{
    "one",
    "two",
    NULL
} ;

which can then be detected just as the NUL character is for strings. Note that for a string, string length and the length of the array the string is stored in are not related, the array need only be at least as long as the string plus NUL terminator. The same is true of using a sentinel value. For example given:

char* strings[20] = 
{
    "one",
    "two"
    NULL
} ;

sizeof(strings)/sizeof(*strings) will be 20, but the array contains only pointers to two valid string-constants before the initialiser after initialisation.

So whether you use the array size or a sentinel depends on the specific data structure you need. The array size is appropriate for constant arrays or arrays with constant number of valid elements. A sentinal might be used for variable arrays, but is not always necessary - you can simply maintain a separate count of valid elements, or may have a sparse array where valid elements are not necessarily contiguous.

Clifford
  • 88,407
  • 13
  • 85
  • 165