0

I'm trying to understand c arrays and why some types (such as chars) require a null terminator but other types do not. Take the the following three arrays

int v1[] = {1, 2, 3};
char v2[] = "hello";
char v3[] = {'h', 'e', 'l', 'l', 'o', '\0'}

How does c "know" when v1 ends, but it needs the '\0' in the character array to know when that ends? What makes the character different than a number in requiring a sort of fake-character at the end to tell the program it's completed? For example, what would happen, if instead of writing char v3[] = {'h', 'e', 'l', 'l', 'o', '\0'}, it was written as char v3[] = {'h', 'e', 'l', 'l', 'o'} (other than getting out-of-bounds when I use a for-loop to detect the end of it).

  • 1
    It's perfectly valid to have an array of chars that doesn't end in a `\0`, it's just not called a string then, and it would be unsafe to use the `str*` functions in `string.h`. – Paul Aug 28 '19 at 02:37
  • 1
    the null-terminator is just needed for C-string functions like strlen, strcpy... If you use it as an array then obviously no null-termination is needed – phuclv Aug 28 '19 at 03:08
  • What hasn't been mentioned is that you can null-terminate the string at any point in the array, not just at the last element, so that you can store strings of varying lengths. Knowing the array length doesn't actually tell you how long the string is. – m69's been on strike for years Aug 29 '19 at 05:03

4 Answers4

4

The question is not what the array does or doesn't contain, or what the compiler "knows" about the array size. The question is simply: how will we use the array once it's created, and in particular, how will we remember (how will the code we write and use know) how big the array is?

Although the compiler always knows, as it compiles the code, exactly how big each array is, that's a compile-time thing, not a run-time thing. There's no mechanism provided by the language that will tell us, at run time, how big an arbitrary array is. So it's up to us (so to speak) to figure it out.

And that's where null termination comes in. It's a convention in C (but a very important one!) that when you have an array of type char, and when you're using that array as a "string', that the string must always be terminated by \0. Every C function that deals with strings -- the predefined ones in the standard C library, along with any string-manipulating functions we might write -- every function depends on this convention. If you have an array of char that's not null-terminated, and if you hand it to a string-processing function, the function will sail right off the end when it tries to figure out how big the string is, with variously undefined results.

By contrast to using a "sentinel value" like \0 to terminate an array, the other principal way to keep track of an array's size is with a separate "count" variable off to the side. That's how we'd probably keep track of your array v1, either by using

int v1size = 3;

or maybe

int v1size = sizeof(v1) / sizeof(v1[0]);

How does c "know" when v1 ends, but it needs the '\0' in the character array to know when that ends?

The compiler is fully aware of how big each array is. (It's the compiler that adds the \0 to v2, so it has to know!) The real question is, how does printf know?

what would happen if... it was written as char v3[] = {'h', 'e', 'l', 'l', 'o'} (other than getting out-of-bounds when I use a for-loop to detect the end of it).

Nothing would happen, except that you (and printf, and strlen, and strcpy, and everybody else) would get get out-of-bounds errors (or the equivalent) when they tried to detect the end of it.

See also What's the rationale for null terminated strings?.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
3

When the C language was made, they made a design decision not to automatically store array sizes in the generated executable. That's the root of all this.

You are mixing up what the compiler knows and what the run-time libraries know. In all your 3 examples, the compiler knows exactly how large each array is and how many elements it contains - it can be determined at compile-time.

However, that information remains internal to the compiler and the size of arrays are not stored in the executable. And so the array size can't get automatically passed on to run-time libraries like string.h, because it isn't stored anywhere.

Instead the library functions get a char*, which is just a pointer to the first element of the array, with no other information provided. In order to know where the valid data ends, a null terminator is therefore needed.

Similarly, if you would swap the array syntax for a pointer, char* p = "hello";, then the compiler wouldn't be able to know the size of what p points to either. If you'd use sizeof(p) you would merely get the size of the pointer and not the array it points at.

Lundin
  • 195,001
  • 40
  • 254
  • 396
2

Strings in C are a special case of arrays. Specifically, they are arrays of char that are null terminated.

In this case:

char v2[] = "hello";

The array v2 is initialized with the string constant "hello". The size of this constant is 6, so the size of the array is 6. This also means that the array contains a string and it can be passed to functions that expect a string.

For this:

char v3[] = {'h', 'e', 'l', 'l', 'o'}

v3 is an array of char of size 5 because it is initialized with 5 elements. It is not however a string because it is not null terminated.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • thanks for this explanation. Why, then, is that not required of non-char arrays? How does C detect the end of a non-char array? –  Aug 28 '19 at 02:53
  • @Shared C does not detect the end of the non-char array. you may declare an array of a single element and try to access any element, getting back some data or Segmentation Fault of you go too far. You basically have to control the size of the arrays yourself using `sizeof(arr)/sizeof(*arr)` construction or dropping arrays altogether and switching to much easier to use `vector` from STL library. – lenik Aug 28 '19 at 02:58
  • @Shared, C does not detect the end of arrays of *any* type, including `char`. It detects the end of *strings*, which is a form that part or all of the *contents* of a `char` array may take. `char` arrays do not inherently need to be null terminated, and sometimes they (intentionally) aren't. – John Bollinger Aug 28 '19 at 03:34
2

There's concept of strings in C, because it's convenient for the text processing. By the common agreement, strings in C end up with '\0' byte to mark the end of the string. Strings can be declared as:

char str[] = {'h', 'e', 'l', 'l', 'o', '\0'}

but to make life easier, there's a commonly used short form of the same:

char str[] = "hello";

You may think of the latter being just an easier to type version of the former.

lenik
  • 23,228
  • 4
  • 34
  • 43