Is it safe to put '\0' to char[] one after the last element of the array?

Viewed 503

I'm interested for some practical reasons. I know C++ adds '\0' after the last element, but is it safe to put it manually ? I heard about undefined behavior, however I'm interested if NULL character is actually the next symbol in the memory?

UPD: I understood, my question is not clear enought without code snippets. So, I'm asking if this is actually save and won't lead to undefined behavior?

const char* a = "Hello";
a[5] = '\0'; //this is wrong, will not compile on most compilers

char* b = new char[5];
b[5] = '\0';

char c[5];
c[5] = '\0';
4 Answers

I know C++ adds '\0' after the last element

This isn't true; There's missing context at least, or a misunderstanding at worst. String literals have an implicit null terminator at the end. The implicit null terminator of the string literal is part of the array. Although it is after the last character of the string, it isn't after the last element of the array; the terminator is the last element of the array. For example, the string literal "" is an array that contains one element (null terminator), and the string literal "a" is an array that contains two elements ('a' and null terminator). "\0" is an array that contains two element (two null terminators).

No other arrays have implicit null terminator, besides string literals.

Is it safe to put '\0' to char[] one after the last element of the array?

The behaviour of reading or writing memory outside the bounds of an array is undefined. It's the opposite of safe.


const char* a = "Hello";
a[5] = '\0';

Assignment through pointer to const is not allowed. This program is ill-formed, which is safe if the compiler refuses to accept the program.

char* b = new char[5];
b[5] = '\0';
char c[5];
c[5] = '\0';

Index 5 is outside the bounds of the array. The behaviour is undefined. Very unsafe.

In C++ if you declare a character array that is initialized by a string literal and you specify explicitly the number of elements in the array you need also take into account the terminating zero character '\0' that is implicitly present in the string literal. For example

char s[6] = "Hello";
      ^^^

This declaration is equivalent to

char s[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };

In C you may ignore the terminating zero character of a string literal when you declare a character array. For example

char s[5] = "Hello";
      ^^^

This declaration is equivalent to

char s[5] = { 'H', 'e', 'l', 'l', 'o' };

But in this case such a character array does not contain a string. So for example using such an array within the function puts like

puts( s ):

or in most standard string functions invokes undefined behavior.

Of course you could declare the array without specifying explicitly the number of elements in the array if you want to initialize it by a string literal and guarantee that the array will contain a string.

char s[] = "Hello";

This declaration is equivalent to

char s[] = { 'H', 'e', 'l', 'l', 'o', '\0' };

and the declared array will contain exactly 6 elements.

Edit: After you appended your question with this code snippet

const char* a = "Hello";
a[5] = '\0';

char* b = new char[5];
b[5] = '\0';

char c[5];
c[5] = '\0';

then you may not change an object using a constant pointer to it like this

const char* a = "Hello";
a[5] = '\0';

Moreover if you will remove the qualifier const (in C string literals have types of non-constant character arrays opposite to C++)

char* a = "Hello";

nevertheless you may not change a string literal. An attempt to change a string literal in C and C++ results in undefined behavior.

In this part of the code snippet

char* b = new char[5];
b[5] = '\0';

char c[5];
c[5] = '\0';

you are trying to access memory beyond the allocated arrays because the valid range of indices is [0, 4] that again invokes undefined behavior.

Instead you could write for example

char* b = new char[5]();

char c[5] = {};

In this case the arrays will be zero-initialized.

Or you could write

char* b = new char[5];
b[0] = '\0';    // that is the same as *b = '\0';

char c[5];
c[0] = '\0';    // that is the same as *c = '\0';

In the both cases the arrays will contain empty strings.

It seems like you are confusing some details.

First, look at the output of this code (C++):

#include <iostream>

int main()
{
    char x[] = {'a','b','c'};
    std::cout << sizeof(x);
}

It prints 3, becuase x is an array with 3 elements. Nothing is added automatically here. Trying to "add" a null terminator at x[3] is not possible, because the array has only 3 elements (last valid index is 2). Trying to access x[3] is undefined behavior.

I suppose you are refering to string literals. They do get a \0 appended:

The null character ('\0', L'\0', char16_t(), etc) is always appended to the string literal: thus, a string literal "Hello" is a const char[6] holding the characters 'H', 'e', 'l', 'l', 'o', and '\0'.

However, that does not imply that you may not insert null terminators in your string literal. It just isnt very practical, because it defeats the purpose of the null terminator to terminate the string (*):

#include <iostream>

int main()
{
    const char* x = "H\0ello";
    std::cout << x << "\n";
    for (int i=0;i<7; ++i) std::cout << x[i];
}

Output is:

H
Hello

(the null terminator is not printable).

(*) Or rather: it is the first \0 that does terminate the string. Any function expecting a null terminated string will stop on the first \0 encountered.


Concerning the update of the question....

const char* a = "Hello";
a[5] = '\0';

The string literal "Hello" is of type const char[6]. It already has space for the null terminator and a[5] already does equal \0. You cannot assign \0 to the last element, because string literals are constant, you cannot modify them. Actually the line would not even compile, because a is a const char*.

The other two

char* b = new char[5];
b[5] = '\0';

char c[5];
c[5] = '\0';

Are undefined behavior, because you are accessing the array out-of-bounds. The last valid index in an array with 5 elements is 4.


After the update it is clear that your misunderstanding is where and when the null terminator is added. This isnt a feature of char arrays, but when you write "foo" then its type is const char[4], already with enough space for the string and the null terminator.

The snippet

const char* a = "Hello";
a[5] = '\0';

does not even compile; not because the index 5 is out of bounds but because a is declared to point to constant memory. The meaning of "pointer to constant memory" is "I declare that I don't want to write to it", so the language and hence the compiler forbid it.

Note that the main function of const is to declare the programmer's intent. Whether you can, in fact, write to it depends. In your example the attempt — after a const cast — would crash your program because modern compilers put character literals in read-only memory.

But consider:

#include <iostream>
using namespace std;

int main()
{
    // Writable memory. Initialized with zeroes (interpreted as a string it is empty).
    char writable[2] = {};

    // I_swear_I_wont_write_here points to writable memory 
    // but I solemnly declare not to write through it.
    const char* I_swear_I_wont_write_here = writable; 
    cout << "I_swear_I_wont_write_here: ->" << I_swear_I_wont_write_here << "<-\n";

    // I_swear_I_wont_write_here[1] = 'A'; // <-- Does not compile. I'm bound by the oath I took.

    // Screw yesterday's oaths and give me an A.
    // This is well defined and works. (It works because the memory
    // is actually writable.)
    const_cast<char*>(I_swear_I_wont_write_here)[0] = 'A';

    cout << "I_swear_I_wont_write_here: ->" << I_swear_I_wont_write_here << "<-\n";
}

Declaring something const simply announces that you don't want to write through it; it does not mean that the memory concerned is indeed unwritable, and the programmer is free to ignore the declaration but must do so expressly with a cast. The opposite is true as well, but no cast is needed: You are welcome to declare and follow through with "no writing intended here" without doing any harm.

Related