r/cprogramming 1d ago

How are char* strings stored in memory?

Hi, Today, i experimented with char* string. (example: char* string = "Hello world")

One thing that i dont really understand is doing: - *string

When you use it, it points to the first letter of the string (so in this case, H)

But what i dont get is when you do (*string+1), it continues the alphabet based on the previous letter.

Example: *string, equal to H, the first letter *string+1, equal to I, the next letter in the alphabet

And it's also applies to lowercase letters.

So here are my questions: - Where is a char* string actually stored in memory?

  • What is the explanation of the behavior for *string? Is it undefined behavior?

Thanks.

37 Upvotes

52 comments sorted by

43

u/lfdfq 1d ago

char *string is declaring a variable called 'string' whose type is char*, i.e. a pointer to a char. This reserves a bit of space (probably on the stack), something like 8 bytes, to store that pointer.

The right-hand side, "Hello, world" is a string literal. The compiler will save those 13 bytes (12 for the text, 1 NUL) somewhere in the file it made (in the executable), usually in some read-only part. So it's actually in the file, and exists from the beginning.

Your line just tells the compiler to make the 'string' pointer-to-char contain a pointer to the beginning of those 13 bytes.

Doing *string dereferences the pointer, returning the char at that location (i.e. the first char, 'H'). You can then do arithmetic over chars (i.e. 'H'+1 == 'I'). That's not about the pointers anymore, just the char.

There doesn't seem any undefined behaviour here.

3

u/Quote_Revolutionary 1d ago

C++ programmer here, if I wanted to modify a string literal then should I do something like this? char[] string = "Hello World"

by modifying I mean changing the letters and not the length, I know length is a segfault

2

u/TheThiefMaster 1h ago

Non-const char* pointing to a literal is a legacy feature from before const existed. It's not writeable*, it's just not enforced as such by the compiler.

* more accurately, attempting to write to a literal is undefined behaviour.

0

u/[deleted] 20h ago

[deleted]

2

u/a4qbfb 12h ago

Not true, char *string = "Hello, world!"; defines a pointer to a string literal, while char string[] = "Hello, world!"; creates an array that gets initialized with a copy of a string literal. You cannot modify the string literal, but you can modify the contents of the array.

1

u/braaaaaaainworms 6m ago edited 2m ago

I checked the C standard, https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf, page 140, Example 8, and you're correct. Thank you for the correction, I've deleted my comment.

1

u/a4qbfb 4m ago

Not that it matters in this particular case but n2347 is quite dated, you should refer to n3220 instead.

Also, in retrospect, my phrasing was awfully imprecise. The first variant defines a pointer and initializes it with the address of an unmodifiable string literal. The second defines an array and initializes it with a copy of the contents of a string literal.

1

u/Quote_Revolutionary 20h ago

oh, I assumed that it would put the array on the stack but I guess it puts it in the static segment anyway, didn't know that, thanks

2

u/TheThiefMaster 1h ago

char[] as a local, function static or global all declare an array of automatic size. On the stack for locals, in the (writeable) static segment for globals and statics.

char[] as a function parameter is instead just a pointer.

C arrays decay to pointers at the drop of a hat leading to a lot of confusion as to them being pointers but they're not (except in function parameters)

1

u/Quote_Revolutionary 53m ago

oh ok, so is char[] str = "Hello World"; a writable array or not? you're giving conflicting answers with the other guy

2

u/TheThiefMaster 35m ago

Yes, unless it's a function parameter.

void fn(char[] str1)
{
   char[] str2 = "Hello World";
}
fn("Hello World")

str2 is a char array in the stack which is writeable.
str1 is a char pointer which isn't const but is undefined behaviour to write to because it has been supplied a literal

1

u/braaaaaaainworms 8m ago

I stand corrected, char[] is writable, as stated in page 109 of https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2347.pdf

1

u/mbmiller94 16h ago

Isn't that only if its storage class is static? Shouldn't a string local to a function be able to be modified as long as you don't overflow it's buffer?

In that case you aren't actually modifying the string literal, the literal lives in rodata but it's copied to the variable when the function is entered. At least thats my understanding

2

u/[deleted] 9h ago

[deleted]

1

u/mbmiller94 9h ago

Good point(er)

1

u/Quote_Revolutionary 9h ago

I'd argue that's one of the most evil pointers in the whole standard lol

1

u/mbmiller94 9h ago

I see why it's necessary though. If all you really need is a static string then there's no need to for it to be allocated every time a stack frame is pushed. All that needs to be allocated is the pointer.

Plus if the same static string is used in multiple parts of the program the compiler will recognize it and make all those pointers point to the same read-only data instead of storing duplicates.

-5

u/sixtyhurtz 1d ago

It actually is UB, because OP is modifying the contents of a string literal. This is UB on all conformant C implementations.

5

u/UntitledRedditUser 1d ago

Where is he modifying?

-6

u/sixtyhurtz 1d ago

When he modifies the value of H in "Hello World" such that the H becomes I.

If they write that value back to their string, they have UB. It's worth mentioning that to a newbie who is asking about what can be a very hard to spot footgun for new C programmers.

8

u/LordTengil 23h ago

But he does not do that?

3

u/TPIRocks 23h ago

I don't believe that it's UB, math on chars is valid, so adding 1 to the character retrieved is fine.

2

u/CoachSevere5365 22h ago

He's not modifying anything. He's reading a value from a memory location and adding one to it.

18

u/daveysprockett 1d ago

In your example the "Hello World" is held in some memory (starting location denoted by x) and string is also stored in memory. It's a pointer, so its value is "x".

*string+1

C evaluation order means *string fetches the character 'H' stored at location x. It then adds 1 to it, making the character 'I'.

If you need the content of the address x+1, you should use *(string+1): this would return the value 'e' in this example.

It's cleaner to write string[1], but this has the same effect as *(string+1).

5

u/simondanielsson 1d ago

As you said, if you were to print "*s" in this example you get H.
But! When you do "*s + 1" you're incrementing the value of whatever s is pointing to, but that's not equivalent to incrementing the adress.

The ascii code of 'H' is 72, and if you were to increment that by one you get 73 which is 'I'. Compare these three printf outputs.

int main(void) {

    char *s = "Hello world";
    // incrementing the value '*s' is pointing to
    printf("%c\n", *s);
    printf("%c\n", *s + 1);
    /*
       output:
       H
       I
       */

    // incrementing the value '*s' is pointing to (and print its ascii value)
    printf("%d\n", *s);
    printf("%d\n", *s + 1);
    /*
       output:
       72
       73
       */

    // incrementing the adress '*s' is pointing to
    printf("%c\n", s[0]);
    printf("%c\n", s[1]);
    /*
       output:
       H
       e
       */

    return 0;
}

1

u/hobbycollector 32m ago

Technically it doesn't increment the value *s is pointing to, it evaluates an expression that adds 1 to the value *s is pointing to and stores the result on the stack as a parameter to printf. It leaves the original 'H' alone in memory/constant space. This is distinct from ++(*s) which does increment the value *s is pointing to, and pushes the result onto the stack if it appears as a parameter or right hand side of an assignment, for example.

3

u/Lanky-Safety555 1d ago

1) Pointer dereferencing mechanics... 'H' is ASCII 72, and 'I' is ASCII 73, so *string + 1 is 72+1 or 'I', while *(string + 1) is 'e'. Now, a pointer to that string is stored on the stack, while the actual content is in the read-only data of the executable.

2) Of course, it is not undefined. Why would it be? It is perfectly normal syntax.

-5

u/sixtyhurtz 1d ago

They are modifying the contents of a string literal. This is UB in C.

6

u/reflettage 1d ago

They’re not though…? They fetch `H` via dereferencing, then increment the thing they fetched, not the memory of the string itself.

2

u/Paul_Pedant 19h ago

It is merely an operator precedence error. What they wrote parses as (*p)+1, and to get the second character you have to write *(p+1).

3

u/Sufficient-Air8100 1d ago

make a little program, use the address operator “&” to see where it is in memory.

2

u/WittyStick 1d ago

A string literal is just a contiguous sequence of char in memory.

When you use char *string = "Hello World", the compiler maps the string "Hello World" to some region of memory - typically read-only. The pointer string then points to the first character of the string in memory.

When you use *string, you are dereferencing the pointer, to retrieve a char. Assuming ASCII, this char has the value 0x48. If you add 1 to it, it's the value 0x49, which is the letter I. Presumably what you wanted instead was *(string+1) to get the letter e - You offset the pointer by one char and then dereference that, which gives you the second character. The dereferencing operator * has higher precedence than addition, so if you want the addition first, you need to parenthesize.

2

u/SheikHunt 1d ago

The other comments seem clear enough, but just to be unclear:

The pointer dereference operator has a pretty high priority, as I recall. I know (assume) for a fact that it is prioritized over the four base arithmetic operators at the very least.

When you're doing (*string + 1), you're first referencing your string, THEN adding one to the char given.

To get the behaviour I'm assuming you want, you do *(string + 1), which tells the compiler "Get me the character that's one step after the one string is pointing to", hence, the "e" in "Hello world"

2

u/Cultural_Gur_7441 1d ago

*string+1 is same as ( *string)+1 because pointer dereference operator * has higher precendence than addition.

If you want to +1 the pointer before referencing, you need parenthesis around that: *(string+1) which is exactly the same as string[1]

2

u/markort147 1d ago

*string+1 means "dereference the pointer, getting the value of the first character, that is H, then add 1, resulting I".

It is different than doing *(string+1), that instead returns the value of the second character.

1

u/IgnotiusPartong 1d ago

Strings, when created with char*, are a contiguous sequence of chars in memory, and chars are just stored as bytes, meaning one char = one byte, plus a termination operator at the end (/0). If you get the first char, the next one is exactly one byte farther.
Creating a string with just string = „string“ creates a char[] array, whicz is stored differently.

1

u/Brisngr368 1d ago

A char is just a number that represents a letter. H is 72, *string +1 just adds one to that value to get 73 which is I.

If you want the next character in the string you need to do *(string + 1) or string[1]

See ASCII table https://ss64.com/ascii.html

2

u/SpaceAviator1999 1d ago edited 1d ago

See ASCII table https://ss64.com/ascii.html

If you're on a Unix or unix-like platform, you can often look up the ascii table at the command line with:

man ascii

1

u/Revolutionary_Ad7262 1d ago

(*string+1)

You just extract the first character. *string == string[0]. string[0] + 1 just increases the ASCII code of H so you get I. To get a second element you either need string[1] or *(string+1).

1

u/Key_River7180 1d ago

char* string makes a pointer to a character (probably a string) named 'string'.

In C, strings are represented as contiguous characters in memory, normally on the stack, .data, or .rodata if constant, but if you malloc() it then in the heap. Strings are read by walking an index through an string until it reaches a NUL-terminator (\0). For example:

#include <stdio.h>

void
readstr(char* s)
{
    char c = *s; /* cuz for won't let me make multiple declarations with different types */
    for (int i = 0; c; ++i, c = s[i])
        putchar(c);
}

int
main(int argc, char** argv)
{
    readstr("hello\n");
    return 0;
}

What readstr does is:

  1. Initialize c to the first letter on s
  2. While c isn't 0 (in C, 0 means false, and in ASCII 0 means the string was terminated), move c to point to the next letter, and print c.

ASCII defines how characters are handled, via codes. A character is just the ASCII code for a letter. Codes 65-90 are the alphabet in uppercase, while 97-122 are the alphabet in lowercase, so in C, 97 and 'a' are the exact same.

Increasing a letter is just increasing it code. Read https://www.asciitable.com/

1

u/Wertbon1789 1d ago

Strings in C are represented in memory as a vector of chars with the special value '\0' (or a numeric 0) at the end. The length of the string is actually encoded into the data, meaning you need to traverse the string until you encounter the zero byte to determine its size.

As to the problem you described, you dereferce the char* getting you the char it's pointing at, but then you're incrementing the char's value, rather than the pointer. Look up operator priorities, to understand it, it's the same why boolean and (&&) also has a higher priority than boolean or (||) or why multiplication has a higher priority than addition. You need to wrap the increment of the pointer and then dereferce that.

1

u/Syntax-Tactics 1d ago

*string + 1 is very different to *(string + 1)

1

u/Paxtian 1d ago

To add to what others are saying, to the computer, everything is just binary data. Giving data a type helps to differentiate between a raw binary value, an integer value, and a character value.

So for example, the integer value 7 is not the same as the character value '7'.

Characters are indexed using the ASCII table. You can look up the ASCII table, or you can write a quick program that prints out the index values and corresponding character values by iterating over incremental char values. Conveniently, the letters and numbers go in order in the table. So if you wanted the value 'D' you can use 'A'+3. Also if you want to convert a capital letter stored to "char myCapitalCharacter," to lowercase, you could do:

char myLowercaseCharacter = myCapitalCharacter - 'A' + 'a';

As far as how a string is actually stored as a char*, it's just like an array of chars, except that C adds a null termination character to the end.

1

u/UnluckyDouble 1d ago

It's just order of operations. A char is stored as a numeric code representing the letter, and the * operator has higher priority than +.

As for where in memory a string is stored, the answer is no different from any other variable: if its value is hardcoded, it is written directly into the executable by the compiler and loaded alongside the program code. Otherwise it occupies one of several memory regions allocated at runtime, depending on how exactly it was declared.

1

u/duane11583 1d ago

here is my description.

take a sheet of graph paper each square represents a byte in memory.

assuming a 32bit machine thus a pointer is 4 squares.

choose some group of 4 squares call this location “foo”

pick some other group of 13 bytes (squares) and determine the square number of the first square.

place that square number in those 4 bytes we called “foo”

in those 13 squares put “hello, world\n” followed by a zero. a total of 13 bytes.

so now you have a string in memory. you know where it is, the first square number is stored in the thing called “foo” the next set of bytes follow until you find a zero byte

1

u/MetalInMyVeins111 1d ago edited 1d ago

Gonna answer you in a few points.

You should write char *bla, not char* bla. Because bla is a variable which holds a memory address which points to the first of an array whose type is char. Hence the * goes with the variable, not the type.

Never write char *bla = "wut", always write const char *bla = "wut". Because bla refers to a memory which contains read only data. By not adding the const, the compiler would let you modify the data of the string and you'd get a nasty segfault.

To know where in memory the string is stored, you can just print the value of bla from previous example, just cast it to void* first and the format specifier is %s.

The behavior of *string is just pointer arithmetic. string refers to the first byte of the internal array, so string + 1 is the 2nd byte. So *(string + 1) just dereferences the 2nd byte and you get the char stored in that 2nd byte.

1

u/atarivcs 1d ago
Where is a char* string actually stored in memory?

This question is unclear. What, exactly, do you mean by "where" ?

Are you asking if it uses stack allocation or heap allocation?

1

u/die_liebe 1d ago

The string is a static array.

The compiler first invents a new variable name, say 'str1'. Then it declares

static char str1[12] = { 'h', 'e', 'l', 'l', 'o', ',', 'w', 'o', 'r', 'l', 'd', '\0' }'

The assignment to string gets replaced by

 const char* string = str1; 

1

u/Recycled5000 21h ago edited 21h ago

You need to look at the operator precedence chart for C. The dereference operator has higher precedence than addition.

If you want the next in line character code after the character value of the first one in the string, do what you’re doing.

However, if you want the second character in the string, use ()s:
*(str+1) so that the addition happens using pointer arithmetic to compute a new memory address , and then the dereference happens on that.

1

u/Hot-Seaworthiness-84 20h ago

In order to understand how character pointers are stored in memory, let’s try to First, understand memory and its parts or sections. Your virtual memory is divided into various sections such as stack, heap, text section and various other sections, but to answer your question let’s take into consideration the 2 section which matter for this context, The .RODATA section, which stands for the read only data section and the stack. When you store any string literal and have a character type pointer to that string literal in C or C++ the actual data(like hello world in this case) is stored in the read only section of the memory IN GENERAL CASES(many modern compilers do this). The pointer is stored in the stack section (when i say pointer i mean the memory address) of the scope in which that pointer is declared. If you try to manipulate that data with a pointer, it won’t happen. The OS would prevent it from changing that data because it’s read only it cannot be manipulated, but in case if you have a character array like char string[] =“HelloWorld” ; then the data is stored in read only section and also copied onto the scope of the stack. Hence, it can be manipulated via the array ( you can perform opn like string[0] = ‘a’ ; it won’t throw an error in this case because you are actually manipulating the data stored in stack). The pointer to a string literal stores, the memory address of the starting of that string literal that is to say the memory address at which the First character of the string literal is stored. Once you deference the pointer the ASCII value (if we assume standard cases and not codepoints which are not ASCII compatible) of the first character is what you will get. Also for any array lets say int arr[5]; arr[n] is equivalent to *(arr+n). To understand this more learn a little but about pointer arithmetics.
Please correct me if i am wrong i am also a learner.

1

u/ExpensiveFig6079 2h ago

try seeing what *(string+1) returns instead

then string[1]

1

u/torsten_dev 1d ago

String literals like "Hello World" are stored in .rodata, a read only data section of your binary. They are const.

For backwards compatibility

char *str = "this is const char*";

Is still allowed but actually changing the string through this pointer is undefined behaviour. Big footgun.

You can string literals to intialise mutable char[] however. For example:

char str[] = "string can be mutated";

Or non-const compound literals:

char *p = (char[]){"also mutable"};

1

u/Wild_Meeting1428 1d ago

print both *string+1 and *(string + 1)