String literals

You may have heard the term “String” before, but if you haven’t don’t worry. They’re not anything super new or fancy… we’ve been using them all along! Any time we’ve put something in double quotes, we’ve been creating string literals!

5; //This is an integer literal
0.874f; //This is a float literal
"Stuff, and more stuff"; //This is a string literal 

OK so… is a string a different type we haven’t talked about? Not quite.

printf("Yeah, some stuff is being printed out!\n");
printf("This is a string. Cool, leave me alone.\n");
printf("You have seen %d Pokemon so far!\n", pokemonSeen);

In the code above we are calling the function printf and passing it a value. This value is a string literal, which is just characters between double quotes. Remember, we can have integer, float, and character literals, and we can set variables equal to those values:

int x = 5;
float y = 0.8243f;
double z = 27.222222;
char a = 'A';

OK, but what about this?

??? = "A string literal! Oh no!";

When we first introduced pointers, we mentioned that we had seen address values (when calling scanf), but had never stored them anywhere. This is a similar situation; we’ve seen string literals a bunch, but we’ve never stored them anywhere for later use. How would we do so?

Let’s ask a related question: what is the type of a string literal?

Turns out the type we need is char * (Remember, we pronounce this as character pointer)

So, if we wanted to store a string literal, we could do so like this:

char* myString = "It's a secret to everyone!";

And we could print it out using a particular format specifier with printf, %s:

printf("%s", myString);

Output:

It's a secret to everyone!

OK, so what are the actual implications of what we just learned?

Well for one, we can now create a bunch of strings and print them out:

    char* songLyrics[5];
 
    songLyrics[0] = "I wanna be the very best, like no-one ever was...\n";
    songLyrics[1] = "To catch them is my real test, to train them is my cause!\n";
    songLyrics[2] = "I will travel across the land, searching far and wide\n";
    songLyrics[3] = "Teach Pokemon to understand the power that's insiiiiiiide....\n";
    songLyrics[4] = "POKEMON!\n";

We’ve made an array of char*, which we now want to print out.

Question

How would we print out the song lyrics above?

OK, cool… since our string literals are just char*, can we use what we’ve learned about pointers to mess around with our strings?

To an extent, yes:

char* anotherString = "A wild Gengar appeared!";
 
char firstLetter = anotherString[0];
char firstLetterAgain = *anotherString;

Question

How would we access the second letter of anotherString using array notation? What about via the dereference operator?

So we can read the values, which is great! But be warned, you can NOT write to a string literal:

You can NOT edit a string literal! Doing so is undefined behavior in C:

//Don't do this! Undefined!
char* anotherString = "A wild Gengar appeared!";
anotherString[0] = 'B';

There’s a specific answer for WHY this is bad and undefined, but we won’t jump into it now. Suffice to say, if we want to have strings we can edit ourselves, we’ll need to do something else…

Character Arrays

Instead, let’s make our own arrays of characters!

char myString[10] = {'E', 't', 'h', 'a', 'n', ' ', 'H', 'a', 'l', 'l'};
 
myString[8] = 'w';
myString[9] = 'k';
 
for(int i = 0; i < 10; ++i)
{
	printf("%c", myString[i]);
}

Output

Ethan Hawk

So now we can read and write to those spots! Cool!

You’ll notice though, that we did something a bit different with the print statement for our character array… what’s different versus our previous print statements? Well, we’re printing out in a loop instead of using a single print statement, and we’re using a different format specifier. This time it’s %c , which is used for single characters.

But why did we do this, instead of just using the following?

char myString[10] = {'E', 't', 'h', 'a', 'n', ' ', 'H', 'a', 'l', 'l'};
 
myString[8] = 'w';
myString[9] = 'k';
 
printf("%s", myString);

Try the code above, and see what you get… maybe not what you expected? You’ll likely see a bunch of garbage printed out that you didn’t expect to see!

What the heck is going on here? Well we can answer that question with another question… how does printf know how many characters to print out? We don’t tell the printf function the number of characters to print, so there’s clearly some other mechanism. We can totally do this:

char* sLiteral = "It is a period of civil war. Rebel spaceships, striking from a hidden base, have won their first victory against the evil Galactic Empire.";
 
//This works just fine, without printing 
//any weird garbage at the end...
printf("%s", sLiteral);

So if the function doesn’t know the size of the string it is printing, what’s going on? Does our string literal also have garbage characters after it, in memory?

Null-terminated strings, aka C-Style Strings

It turns out that string literals have a special character at the end, which looks like this: '\0'

This is the null character, or null-terminator. It is NOT the same as the zero character ‘0’, and it isn’t the same as the NULL value used for grounding out pointers (yes, confusing, just keep it together!).

When a string literal is created in C, the compiler creates enough space for its characters and adds an extra byte at the end for the null character. This is so any functions that use these strings can know where the end of the string is. The binary interpretation of ‘\0’ is 00000000, a byte of all 0s.

char* stringThing = "Amphibia";
‘A''m''p''h''i''b''i''a''\0’
012345678

OK, that’s a neat factoid. But what does that have to do with the garbage happening when we printed out “Ethan Hawk” as a string? When we printed out each individual character, we were using a loop to print out exactly the number of characters in the character array. But when we tried printing as a string, printf tried to stop printing based on where the null-terminator was located. Which, if you’ll remember…

char myString[10] = {'E', 't', 'h', 'a', 'n', ' ', 'H', 'a', 'l', 'l'};
 
myString[8] = 'w';
myString[9] = 'k';
 
printf("%s", myString);

Isn’t in our character array! So printf pulled a Finding Nemo, and just kept swimming until it found a null terminator.

Character Arrays vs Character Pointers

OK, so we now know that strings in C are null-terminated. And if they’re not null-terminated, they’re just an array of characters.

How are these two things different, in practice?

char* pChars = "The Owl House";
char aChars[14]  = "The Owl House";

Well for one, we know we can’t edit pChars. We said previously that this is undefined behavior (which we’ll explain later). We CAN edit the characters in aChars though:

aChars[4] = 'C';
aChars[5] = 'a';
aChars[6] = 't';

We can also set pChars to point at a completely different string/address:

char* pChars = "The Owl House";
 

But we cannot do so with aChars, because it is an array name. We’d get a compile error:

//Just some uninitialized char pointer
char* someOtherChars;
 
//NOPE, we'd lose our array in memory! Can't do that!
aChars = someOtherChars;

Initializing Character Arrays

So we’ve explored how these are different… how are these similar?

char* pChars = "The Owl House";
char aChars[14]  = "The Owl House";

For starters, we can read the characters in both pChars and aChars:

char firstLetter;
 
firstLetter = pChars[0];
firstLetter = aChars[0];

Totally legal, a-OK. We can also print out either of these, via printf:

printf("%s", pChars);
printf("%s", aChars);

“But wait!” you cry out, because you’ve been paying attention, “I thought aChars didn’t have a null-terminator!?!” It is just an array of characters? Remember how we could initialize arrays using the following syntax:

//Totally legal
int someNums[5] = {56, 324, 932, 1, 8};
 
//BAD, CANNOT DO THIS!
someNums = {1, 2, 3, 4, 5};

We can use this syntax when initializing the array with values for the first time when it is created, but we can’t do so later. That’s just C syntax. The same is true for character arrays:

//Totally legal
char aChars[14]  = "The Owl House";
 
//BAD, CANNOT DO THIS!
aChars = "Amphibia too!";

The first line is like an array initializer. But we can’t perform the second line, the same way we couldn’t perform the second line in the integer example above. We can initialize an array when it is first created, but not afterwards.

Which… didn’t answer the question of why we are able to print out both our character array and our string literal. Well when you initialize an array in C, any remaining spots not specified by the array initializer are set to 0:

 
//The two integers on the end are 0
int someNums[5] = {1, 2, 3};
 
//someNums[3] == 0
//someNums[4] == 0

Notice something interesting about our character array? (Hint: count out the number of characters!)

char aChars[14]  = "The Owl House";

Since we’ve specified an array of length 14, but only supplied 13 characters for the array initialization, the last character is filled with 0, which happens to be the null-terminator for strings! So, we’re covered, and can safely supply this character array to C functions that expect a null-terminated string, like printf.

Question

What’s dangerous about the following line of code?

char favoriteShow[4] = "ATLA"; 

String Functions

There are some string functions in the C Standard Library that deal with nul-terminated strings. You can access them via:

#include <string.h>

Some of the more useful ones:

Function PrototypeDescription
size_t strlen(const char *string);Returns the length of the string, which is the number of characters in the string. It does not include the terminating 0.
char *strcpy(char *destination, const char *source);Copies the string pointed to by source into the string pointed to by destination. Destination must have enough space to hold the string from source. The return is destination.
char *strcat(char *destination, const char *source);Concatenates (joins) two strings by appending the string in source to the end of the string in destination. Destination must have enough space to accomodate both strings. The return is destination.
int strcmp(const char *s1, const char *s2);Compares two strings lexicographically (i.e. alphabetically). If string1 is less than string2, the return value is negative. If string1 is greater than string2, then the return value is positive. Otherwise the return is 0 (they are the same.) UPPERCASE is considered different than lowercase.

How might we implement strlen, if we were going to do so ourselves?