JJGong
JJGong

Reputation: 129

C when should char** be null terminated?

Couldn't find any questions on StackOverflow that addresses this question.

I realize that char* arrays don't have to be NULL terminated, but was wondering when you would want it to be?

For example, when debugging my code, I use a lot of printf() to see if my variable is correct at certain stage of the code.

I have an char** values that holds 4 char*, I made the last char* NULL. With NULL terminating, printfs from values[0] to values[3] give me this note: names is just another array that I print right after I finish printing the values array

Testing values1[0]: %HOME/bin:%PATH
Testing values1[1]: /%HOME/include
Testing values1[2]: /%HOME/lib
Testing values1[3]: (null)
Testing names2[0]: PATH
Testing names2[1]: IDIR
Testing names2[2]: LIBDIR

I have an char** with 3 char*, all of which are valid char*. Without NULL terminating, printf from values[0] to values[3] gives me this (names doesn't show)

Testing values1[0]: %HOME/bin:%PATH
Testing values1[1]: /%HOME/include
Testing values1[2]: /%HOME/lib

I think when printf(...., values[3]) would be an undefined behavior, such as printing a garbage value, but as shown in the output above, everything including and after printf(...., values[3]) seems to not have been executed.

Upvotes: 1

Views: 4445

Answers (5)

In C99 (& POSIX), the only array of char* which is required to be NULL terminated is the argv second argument to main. Hence your main function is (or should be) declared as

int main(int argc, char**argv);

and (at least on POSIX systems) it is required (and enforced by the runtime crt0) and you should expect that:

  • argc is positive
  • argv is an array of argc+1 pointers, each of them being a C string (so ending with a \0 byte)
  • its last element argv[argc] is required to be the NULL pointer
  • two different pointers in argv (i.e. argv[i] and argv[j] with both i and j being non-negative and less than argc+1) are not pointer aliases (that is the same pointer value)

Of course, some libraries may also have functions whose argument might have similar requirements. That should be documented.

Upvotes: 0

Lundin
Lundin

Reputation: 214890

There's a lot of confusion here. First of all:

  • NULL refers to a null pointer constant which you only use for setting a pointer to point at "nothing", an invalid memory location.
  • Null termination, sometimes spelled "nul" to not confuse it with the above, means putting a zero character '\0' (sometimes called "nul character") at the end of a character array to state where your string ends. It has nothing to do with NULL pointers what so ever. A better name than "null termination" might be zero termination, as that is less confusing.

Now as it happens, 0, NULL and '\0' all give the value zero, so they could in practice be used for the wrong purpose and the code will still work. What you should do, however, is this:

  • Use 0 for integers.
  • Use NULL for pointers.
  • Use '\0' to terminate a characer array and thereby making it a C string.

Next matter of confusion:

I have an char** values that holds 4 char*

Pointers do not hold values. Arrays hold values. Pointers are not arrays, arrays are not pointers. Pointer-to-pointer is not an array, nor is it a 2D array.

Though in some circumstances, you can get a pointer to the first element from an array.

An array of pointers to strings of variable length can be declared as: char* string_array [N];. You could iterate through this array by using a pointer-to-pointer, but that's not a good idea. A better idea is to use array indexing: string_array[i].

Overall, there exists very few cases where you actually need to use a pointer-to-pointer. Returning a pointer to an allocated resource through a function parameter is the normal use for them. If you find yourself using pointer-to-pointers elsewhere, it is almost a certain indication of bad program design.

For example, one particular case of very wide-spread but 100% incorrect use of pointer-to-pointer is when allocating 2D arrays dynamically on the heap.


when should char** be null terminated?

Never. That doesn't make any sense, as explained above. Your should most likely not use char** to begin with.

You could however end a character pointer array with NULL, to indicate the end of the array. This is common practice, but don't confuse this with zero termination of strings.

Example:

const char* str_array [] =
{
  "hello",
  "world",
  NULL
};

for(size_t i = 0; str_array[i] != NULL; i++)
{
  puts(str_array[i]);
}

Upvotes: 7

JJGong
JJGong

Reputation: 129

I got an answer from my TA, as a response to "when should char** be null terminated?" which I find reasonable. It would be cool if there are other reasons to why you would do this.

"This is a good conceptual question, and you can think of it as analogous to why C strings are null-terminated.

Suppose you didn't want to explicitly store the length of an array (because it's extra data for you to manage and pass around, etc). How would you know where the array ends? The NULL at the end acts as a sentinel value so you can simply iterate over it until you reach the magic end-of-array value.

If you have a fixed array size or are storing it in some other way, the NULL end isn't necessary."

Upvotes: 3

Sourav Ghosh
Sourav Ghosh

Reputation: 134396

As per C11, chapter §7.21.6.1, fprintf(), %s conversion specifier

s If no l length modifier is present, the argument shall be a pointer to the initial element of an array of character type.

So, you may not pass a NULL as the argument. It invokes undefined behavior. You cannot predict the behaviour of UB.

What you can do is, put a check on the argument to be != NULL and then, pass the variable. Something like

 if (values[n])
   puts(values[n]);

Upvotes: 0

Umamahesh P
Umamahesh P

Reputation: 1244

Each string needs to be null terminated. Easy option would be to memset the full array with null (i.e. 0 or '\0').

Alternatively, if you don't want to null terminate, then you need to keep track of the length of the string.

Upvotes: 0

Related Questions