
Lesson Description
The "Readonly vs Writable Memory" Lesson is part of the full, C Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:
Richard walks through the remaining implementation for the main function, which uses the returned path from the to_path function and prints it to the console. Using the "char *" notation for the request string will cause an error because C stores that string in read-only memory. Refactoring to bracket notation instructs the program to store the string in the application memory, allowing it to be mutated.
Transcript from the "Readonly vs Writable Memory" Lesson
[00:00:00]
>> Richard Feldman: We've now got our const char for the DEFAULT_FILE, which is called index.html. We've got our two path function, which takes in the request and then actually mutates it in place, and then ends up returning it. And then back in main, we have char *req = blah, blah, blah.
[00:00:14]
So then we're gonna call char *path = to path (req), and then this is gonna give us the memory address of that thing. It's gonna do those two for loops that we wrote. It's gonna translate all this back into path.html, this is gonna return the memory address of the beginning of our brand new blog/index.html, which as we know is in the giant global mutable array that is memory.
[00:00:39]
It's actually like these bytes right here because we went and just stomped all over this initial request and wrote the index.html right here. And then got back this address right here that's the beginning of this thing which all works totally reasonably, it's just really not what I would do in most languages.
[00:00:53]
Like in JavaScript, for example, strings are not just arbitrary ones and zeros in memory. In JavaScript, strings are immutable. There is no way in JavaScript to say, go in and stomp all over these bytes. Go in and mutate the string. JavaScript just doesn't support that, and that's the case in plenty of languages.
[00:01:07]
But in C, they just like, yeah, it's all bytes. Go nuts, do whatever you want. Now, if you run this code exactly as is what you're gonna get, at least on Mac OS, is you get what's called a bus error. This is another one of those inscrutable errors, much like a segfault.
[00:01:22]
Does anyone have any guesses as to why you would get an error when you write this code? I'll give you a hint, it's not because our logic is incorrect. It's not that we made an arithmetic error. It's not that we are doing something that can't work. It has more to do with where this actual memory is located.
[00:01:40]
>> Speaker 2: I was wondering if it's because you're tampering with data section memory.
>> Richard Feldman: That's exactly right, yeah, it's because you're tampering with [LAUGH] a data section that you're not allowed to tamper with. So remember earlier on, way back at the beginning, we talked about this string is going to get stored in the binary, in a special section of the binary that's for things like strings.
[00:02:00]
Well, that section of the binary is marked as read only by the operating system. So, yeah, in theory, if we had this string somewhere other than that section, we could go in and like mutate this thing and stomp all over it. That would be totally fine. But because this is a string literal the way that we've written this here the operating system's gonna be like, well, hang on a second.
[00:02:19]
You're trying to stomp all over some read-only memory in the binary that you can't do that, no error. And the error that it gives, at least on Mac OS, is bus error. I think it's a different error on Linux, but point is the operating system is not okay with you mutating that memory.
[00:02:31]
Other memory you can mutate, but not that one. Now, because the way that we've written this, it is read-only, this doesn't work. But if we make one very slight change, which is switching from char star to array brackets here, then it works just fine. Again, I mentioned this a little bit earlier.
[00:02:52]
It's not something we're gonna go into a ton of detail on, but I do wanna mention that essentially what this syntax, brackets means is, in terms of the actual contents of what this thing has in memory. It's, once again, just an address. No problem, nothing fancy going on here.
[00:03:07]
It's just an integer in memory. The real difference here is just that what this is saying to C, like the C compiler, is I want this to be a memory address, but I want whatever comes after the equals to be stored inside the main functions memory. Do not put it in the read only section, like you normally would.
[00:03:23]
When I use these square brackets, I'm basically saying I want an array on the stack. I want a local, like variable, like actual part of this functions memory, as it's running, is this thing, so I can mutate it, I can do whatever I want with it. So if you see C code in the wild, that is using this.
[00:03:38]
That is a potential reason that they wanna do that is they want to explicitly say, I don't want this to be read only memory. I want it to be local to the function so that I can mutate it without having my operating system blow up at me. Okay, so to summarize all the things we talked about in part three, we started off by just looking at how we define new functions.
[00:03:55]
And there was that little gotcha there of the, if you define the function before, sorry, if you call the function before it's defined, you'll get a compiler error. There's two ways to resolve that. Either you can just move it up earlier so that it's defined before it gets called, or you can forward declare it with a semicolon.
[00:04:09]
Just write out the type and the name of the function, put a semicolon at the end, either way works. Talked about, two different styles of iteration. We saw the wild loop where we just sort of advancing the memory address of the start and the end of the path.
[00:04:21]
Talked about copying memory with the memcopy function. So we're just taking some bytes that we got, as long as they're not in the read-only section, and just stomping all over them, just saying, yeah, just here's my source, here's my destination, boom. Just put these bytes over here now.
[00:04:33]
And then finally, we talked about read-only memory. So putting all these things together, we were able to take that part of the request and translate it from slash blog in the middle of this like get an HTTP 1.1 all these headers, and get back out the string that we want, which is blog, slash index, dot HTML.
[00:04:51]
Whether or not it had a slash at the end of the blog in the first place. Cool, any questions before we get into the exercises.
>> Speaker 3: I have one semantic question.
>> Richard Feldman: Sure.
>> Speaker 3: I noticed in like in the function you were writing, we had both start and end as addresses in memory, but you were accessing them with the brackets and like the zeroth index.
[00:05:15]
>> Richard Feldman: That's right.
>> Speaker 3: But then incrementing it without doing any kind of increment to the index.
>> Richard Feldman: Right?
>> Speaker 3: Why is that?
>> Richard Feldman: So when we're accessing memory, we're using this bracket syntax, right? And we're saying, okay, so start as a memory address. Start bracket zero basically means go and look at this, the address of this memory and the bracket 0 is essentially saying, I want you to go get me whatever the first element of that array is.
[00:05:38]
So this is where the actual type comes into play and makes a difference. So for example, if I said instead of char star, I said int star, what that would affect is that would affect what bracket zero means. So that would mean bracket zero says int is 32 bits, whereas char is eight bits start bracket zero, if start is a char star, means go get me those eight bits and put them right here.
[00:05:59]
So give me one byte and put it right here. If I change this to instar and change nothing else about the program, well, first of all, I'd get a compiler error because it would say type mismatch here. I'd have to cast it to instar, but if I cast it to instar, it would still be the same exact integer in memory, this is the bytes of the beginning of that thing.
[00:06:15]
And that byte the address is exactly the same, no matter whether I'm treating it as a char star, int star, or any other kind of star. The important part here is that the bracket zero is gonna say, either, go get me eight bytes from char star, or if it's int, it's gonna say, go get me 32 bytes.
[00:06:30]
Sorry, bits, eight bits or 32 bits. It's basically a way of saying take this address and just go look at what's there and give me the next N bytes. And how many bytes we're getting is determined by the type back up here. So the bracket zero is a way to turn it from the address of the array into one of the elements in the array, whatever that thing's type is by just going and accessing that much memory.
[00:06:54]
Meanwhile, start equals start plus one is working on the address itself. So because there's no brackets, we're not actually going and looking at that address. We're not going into that memory and seeing what's there and like reading some number of bytes out of it, we are with the brackets.
[00:07:07]
We're actually changing the address itself. We're saying, yeah, start used to be this integer. And now we're just incrementing that actual integer of memory address to move it over one spot in the array.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops