In C a string is defined as a nul-terminated sequence of
characters. In your example, neither b nor b1 constitute
a string.
Array b does not have room for a nul at the end of the
three characters.
Array b1 does have room for a nul at the end after the
two chars but as you have not specified a value for the
third element in the array it may not contain a nul.
If these lines of code are at global scope - outside of
main() or any other function - then the 3rd element in
b1 will be default-initialized to binary zero which is
the nul-terminator character.
If these lines of code are at local scope - inside of
main() or any other function - then the 3rd element in
b1 will be uninitialized.
Some compilers - such as VS/VC++ - will store a special
character in uninitialized variables during Debug builds.
In Release builds the contents will be random.
You cannot use any C functions which operate on strings
with ill-formed character arrays. Attempting to do so
is a programming error and will yield unpredictable
results.
The above comments also apply when working with a C style
string (a nul-terminated sequence of characters) in C++.
C++ also has its own string class std::string which is
the preferred object to use when working with strings
in C++.
Note also that in C++ you will get an error from this line:
char* p = "ab";
as C++ will not allow assigning a pointer to a constant
object to a pointer to a non-const object. The string
literal "ab" is a constant string which cannot be altered.
In C++ a cast would be required, either a C cast or a C++
cast, to override/discard the const-ness of the string:
char* p = (char*)"ab";
char* p = const_cast<char*>("ab");
However, doing this creates the possibility of errors
at run time so it is better to define the pointer as
being to a const string:
char const *p = "ab";
This will allow the compiler to catch attempts to alter
the constant string, rather than wait for an exception
at run time.