 All right, welcome back to Operating Systems. So this class on Friday will, thankfully not be in this room, so on Friday we'll be in NL six to line up with the other sections, so I don't have to run. So that room, oh nice, okay. That room has a bit more air conditioning, so that'll be nice on Friday. So we're at least able to do that. So today we're going to talk about libraries. This is another one of the lectures where you don't have to understand 100% of the things I say in the lecture. The core course content you'll be 100% responsible for, we'll start next lecture, but we're going to be building libraries and libraries are important, so we should at least talk about them a bit. So first, we might actually be able to answer what is an operating system? So now we know that the kernel is definitely part of the operating system, but what else? So for instance, is macOS, iOS, iPadOS, watchOS, TVOS, all different operating systems, or are they actually the same one? So who here thinks they are exactly the same and this is marketing? No one? Shaky? Who thinks they're all distinctly different operating systems and they have nothing to do with each other whatsoever? One in the back, two, who thinks they don't really know or aren't here? Hint, that should be like everyone else. All right, no one wants to vote today? Cool, so that'll be fun. Well, so the definition of operating system really depends on what applications you're using and what applications you're using depends what libraries you need and yeah, what libraries you need to actually run that application. So for instance, like you might have three applications here, Network Manager, LibreOffice, which is like the Microsoft Office open source version, or Firefox. All those applications will use different libraries because they do slightly different things. So for instance, Firefox, well, it will use a GUI, it won't be a terminal application, so it will probably use something like a display server to actually show you contents of a window and that's like in Linux, the default one is called Wailin nowadays, but programming for that is really bad because it just gives you straight byte buffers that you have to manipulate. So you might use something like a GUI toolkit, something like JDK. So Firefox actually uses JDK to render on any of the UI components that you see. So Firefox would probably use this toolkit. It might do some special stuff that renders directly to a window, so it might directly use Wailin, which might be, you know, and the toolkit would use Wailin, so it might be a combination of directly using it and indirectly using it. It might also use a system daemon, which is just another way to interact with hardware in user space and make requests to the operating system. So it has a silly name, but that's what it's called. So Firefox might use all these libraries. Those libraries might use the standard C library or Firefox probably uses the standard C library directly as well. Other programs like LibreOffice, well, it probably won't use the system daemon library. It might just use the tooltip or it might just use the GUI toolkits. And Network Manager might not care about anything and not render any GUI applications, but just talk to a system daemon and libc directly. So all these different applications would use different libraries because they all do slightly different things. So what exactly you call an operating system just depends on the application. For instance, Android and your Debian virtual machine both use a Linux kernel, but you would probably consider them different operating systems that are completely separate, even though they use the same kernel. That's because they use wildly different applications. Your Android phone are going to use Android apps which interact with a certain way. Well, on Debian, you're lucky to have a graphical interface and you might just use terminal stuff. And if you do, it will be different windowing systems and things. But in fact, you might actually consider Debian and Android the same operating system if you only use terminal applications. So Android comes with the standard C library. You could run anything you've written in 105 on your Android phone if you really want to because all it uses is standard C library and that exists on Android. It's just a pain in the butt to actually execute it and compile it on your phone, but if you really wanted to, it would be there. So for terminal applications, you might even consider Debian and Android to be the same operating system while any same person that uses graphical frontends would probably not consider them the same operating system. So it really depends on the applications. That's why it's really hard to define what an operating system is because the answer is it depends. Kernel, definitely part of the operating system. What else is part of the operating system? Eh, depends. So you might also hear the term like Linux distributions, but Linux is just a kernel and there's more stuff to actually use the system. So like some people call it GNU slash Linux. I'm not a big fan of that name, but that is what some people call it because GNU distributes the standard C library, all the common utilities like the compilers or at least GCC, you might not use that compiler, but that's included as part of an operating system if you like more like a developer. But an operating system really just consists of kernel and any libraries you need to run your application. So in the case of iOS versus watchOS versus iPadOS, the kernel on all those is gonna be exactly the same, but the applications are all slightly different. So 95% of them are gonna be the operating systems are gonna be the same, but they're meant to support different applications because Apple has a certain way that they want applications. So it's actually fair to call them all different operating systems. And most of the difference between Linux distributions too is just the package manager. Most of the software is actually pretty much 99.9% the same between all Linux distributions for that. All right, so now in order to talk about libraries, we need to talk about how compilation works in C. So this should be of no surprise to you. So say you write a program that uses four C files. Well, the compiler will compile each of those C files individually. So for each dot C file, you would get a dot O file and that would be what your compiler does. Or if you're using make and you didn't invoke the compiler yourself, this is what's actually happening. So each C file will get compiled in a dot O file and then they'll all be linked together to form your executable. And basically that linker will just smash them all together into a single file. And then that is your executable that can execute. And for fun, that executable we know is an L file in Linux. The dot O files are also L files. Everything's a damn L file on Linux. So that's normal compilation. Any questions with that? So hopefully you've made an executable that consists of a few C files before. Yep. So the question is, where do header files fit into this? So the header files would be included in one of these dot C files. And all the compiler will do is find where that header file is and read it. And just essentially copy and paste that header file into the C file if you want to think of it that way. For the C compiler, it's the same thing. But yeah, way at the back. So this is just how it works in C. One C file gets compiled and then they're all linked together at the end. So it's just how it works. Otherwise, you could make a C file that includes every other C file and just get away with this and have one big C file if you want. You can do that. There's nothing to say you can. It's just kind of ugly. But this is the way normal compilation looks. So there could be different functions in each of these C files. And then in the executable, you can use the functions between them. So static libraries are basically just a way to reuse those dot O files. So for instance, let's say those same four files, say three of them I want to reuse. So say I want to reuse util.C, which gets compiled to util.O, foo and bar. So say those are functions I want to use in a different executable. Well, I can archive them all together. That's just another step to kind of smush them all together into this dot A file. So this lib.A is called a static library. It's basically just smashing all the dot O files together. And essentially, it just concatenates them together. It's just a combination of all of them. So now, instead of linking all the dot O files together to form your executable, you're going to link together your main dot O with the lib. And that lib.A file will have all those other dot O files. So it'll essentially be the same thing. Your executable will look exactly the same. The only difference is, hey, if I want to have a different main to create a different executable, well, I just have to change one file. I can leave that static library alone. So you can reuse it over and over again. So that's static libraries. So if there's static in computer science, there's probably dynamic. So dynamic libraries are more meant for reusable code. They work pretty much the same way except for one little caveat. So for example, the C standard library is a dynamic library. And you'll know what a dynamic library is on Linux because it ends in .so. We kind of saw that already in the previous lecture. And basically, it is just a collection of dot O files containing functions. And we got a question. Can I explain static libraries one more time? OK. So static libraries, they behave the exact same way as. So in normal compilation, each C file gets compiled to .ofile. And then they all get linked together to form your executable. The only difference in a static library is I can make a static library that just consists of those .ofiles. And instead of saying, I want to link together these four .ofiles. I'll say just link together main and the static library. And the static library has the three .ofiles in it. So that way, if I want to reuse those three .ofiles, I kind of package them all together so I don't have to specify all three every single time. Yep. So we'll get to that. We'll get to that. OK. So yeah, that's static. It's just another way to reuse them. Otherwise, it behaves exactly the same. So yeah. So here, dynamic libraries, they'll end in .so. It stands for shared object. And basically, it's the same idea. It's just a collection of .ofiles that have all the definitions for the function so it contains all the instructions to actually run printf or whatever. And the idea behind that is many applications can use the same library. So pretty much every executable on your Linux machine will use the standard C library. So instead of each one opening the standard C library and having to load it into memory each time, well, the operating system can be a bit smarter. It can see that, hey, application one is using the standard C library. I'll load it into memory. And then when another application uses the exact same library, it can say, hey, I've already loaded it into memory. And I will go ahead and share it with that application without having to reload it again from scratch. And we'll see how that's implemented as soon as we get to virtual memory. So it gives you some nice benefits there, you can imagine. I don't have to load the code more than once. And yeah, to do the Discord, it's basically just concatenating all the functions just for convenience, for static libraries. So the difference between static and dynamic is otherwise they're the same. So anything I compile, the like util.c, I compile until util.o, well, I can link them all together, which will, again, throw them all together into this lib.so file. And the idea behind this so file is I don't include it in the executable. I look it up whenever I run the program. So now if I just have .main, I don't need to include anything else in the executable. I can just make the executable directly from main. And then at runtime, like what we saw when we straced our hello world example in C, at runtime, whenever you actually run your program and it becomes a process, that's when it looks up. The standard C library finds out where it is and actually finds what function to call. So the only difference between static and dynamic is when you actually resolve printf or something. Yep. Yeah, so one thing, they're a memory optimization. Another thing is essentially when you do it statically, you're essentially just throwing code into your executable over and over again. So you can imagine printf, like if every single executable has its own copy of printf, yeah, it would waste memory. So that's one of the main reasons to use dynamic libraries. OK, with that, so there's this little command line utility. If you want to see what dynamic libraries your executable uses, it's just called LDD. So you can use that for anything, just for fun. So we can see that. So we could like, some example will run later. We could run LDD, some alloc. So there'll be some things here that are like fake library files, which have to do with the dynamic loader in Linux. So these two are fake. They don't correspond to the file. But we can see that this one, that's the standard C library. So I use the standard C library for this application. If you LDD anything, anything probably uses the standard C library. We could see is code here. It's not a real thing. LDD, Firefox, sure. It's not a dynamic, OK, so for something else, LS. You can LDD it, see what libraries it uses. So it uses libc, but you can see it uses some libprc, v2. It's regular expression library, but you can figure out what things applications actually use. So it might be a thing. Won't be tested on using that questionable whether or not you'll ever use LDD for this course. But again, if you get into low level systems programming things, it might be something useful. So static and dynamic libraries. So there are pluses and minuses to both. So compared to static and dynamic libraries, static linking prevents reusing anything. So like you said, with the memory savings, you're basically copying and pasting code into your executable every time. So again, you might have thousands of copies of printf. That's going to waste this space and memory space and there be lots of duplicates. Another thing is with update, code does not stay the same. Code constantly evolves and changes over time. But if you essentially copy and paste code into your executable whenever you compile it, it's frozen at that point of time. So if you need to update some library, you would have to recompile your executable. So like if you used static linking with the standard C library, if they updated the standard C library because there's some vulnerability in it or it was just wrong before or had some bug, well, now you have to recompile your application in order to use that new one because it's essentially locked in place whenever you compile. So could we think of if updating is a pain for static libraries, can we think of any issues with using dynamic libraries? Yep. Yeah. So generally changing software makes it better, but sometimes it doesn't. So the opposite problem happens is if we use a dynamic library and we're looking it up whenever we run, well, we might use a different library from one execution to the next. And if there is a bug in, say, the standard C library and everyone's using it, well, suddenly every single program on your machine is now vulnerable. So that's not great. So typically there's a double-edged sword with using dynamic libraries. So not having to recompile for updates is great, but bad updates, making your code not work anymore is not so great. So there's kind of like an ebb and flow whether or not developers prefer static versus dynamic libraries just because of that trade-offs. Because if you're using a dynamic library, that's good. There's lots of good savings. You get updates. But you really, really, really need to be very careful if you are developing a library because lots of people depend on your code and you really need to know what you're doing because if you break something, you break it for everyone and people will probably stop using your software. So that's not good. And then eventually if you disappoint people enough times, they will start statically linking your code and just have something that works. Then it's going to get really old probably because they're never going to update it because it works. And then it's going to just be some software development maintenance nightmare. And then people are going to use dynamic libraries and the whole cycle is going to go again. So we said we'll break it. We don't know what breaks it. So I will illustrate what breaks it. So there are subtle things that will break in a dynamic library. So the API and ABI has to remain consistent or else really bad things will happen. So as a compiler detail, something you don't really need to know for this course but you kind of do because we'll be writing libraries. But hopefully this is a warning not to do this. So in C, if you create a struct and you have fields in it, there is a very defined way that C will lay that struct out in memory. So it is defined. So no matter what compiler you use, they all agree with how to lay out a struct in memory. So if your code compiled in GCC interacts with something with Clang, it all works together because they all agree. That's how computers basically work. Same thing for calling things in assembly like the calling conventions. Everything has to agree. So the order of the fields of the struct determine how it's laid out in memory, which is part of the ABI, which is something you don't typically think about when you're coding. So if you change the order of two fields in a struct, it will change how it's laid out in memory. And if it changes how it's laid out in memory and then two things can disagree, then that is a recipe for disaster. So let's make a concrete example of that. So consider the two structures. So say I have a library that's defining a point, and a point just has an x and a y. So I could define struct point in version one of my library and say int x, or sorry, int y, int x. So that's perfectly fine way to define a struct. Anyone disagree with my structiness? That's not a word. But that's a perfectly good struct. So later I could decide that, hey, I don't really like reading y than x. I'd rather read x than y. So I might later change the struct to be this. I might just change the order of the field so it reads a bit better. But realistically, I just changed how the compiler lays that out in memory, even though this wouldn't change the API because there is a struct. It has two fields, x and a y. It doesn't change between the two versions. All I did is change the order. So in this course, we'll come up to the word offset sometimes. So offsets, you can think of indexes. So if you have an array, it has an index that starts at zero. And it's how many elements from the beginning of the array there are. Offset is the same idea, except offsets are always done in bytes so that it's just super generic. So in this case, since ints are four bytes, for version one, the x field would be offset by four bytes from the start because it's the second int. And y would be at offset zero because it's the beginning. But in version two, if we were just talking about x, it's now at the beginning so it would be offset zero bytes. And because we did this, it will cause some issues. So from last year, just in case that was confusing, I made this too. So both version one and version two of the point, if offsets are confusing, you could think of them both being compiled. By the time it gets to the compiler, they're pretty much both look like int array of two elements. So both of them look the same. And the only difference is which one is which. So version one, it would translate y to be array index at zero. And x to be array index at one. While in version two, it would be x is array index at zero and y is array index at one. And oh, yeah, sorry, the question in the chat. All your apps have to be updated for new libraries. That's only if you compile them in statically. If you do it dynamically, as we'll see with this, you get the updates which may or may not be good. All right. So any questions about this point changing? It just changes where it is in memory. So again, here, y is array zero. And later, y is array one. OK. So this is where issues happen. Oh, and then we'll go into our library. So this is our library. It's going to consist of four functions, point create, which takes an x and a y and returns a pointer to a struct. And then two accessors, get x and get y and then point destroy. So we can even look at the code for them quickly. Hopefully we have enough time. So in version one, my point code, I will hide this. So it defines point create, which just does a malloc of the struct. And then get x and get y and then point destroy. And that's it. In version one and version two of my library, my code looks exactly like this. I never changed the code. All I did is change the struct. So yeah, so we'll see how this causes an issue. So just that one change messing with the memory layout is going to cause issues because we're going to get some type of disagreement. So here's the code that uses the library. So I include point.h. And then I create the point. This will use the library because this is part of the library. So it won't be included in my main executable. It will call the library. And then here I have a printf that says that outputs the x and the y using the library accessor functions. So this does point get x, point get y. And then later I do printf with printing x and y using the struct directly. So I'm accessing p arrow x and p arrow y. So this will include those indexes into this executable itself. And you can kind of see where I'm getting at with there being some type of mismatch and bad things will happen. So let's go ahead and run this. So let's ignore that for a second. So if I go ahead and run this, so I compiled and linked different versions together so you can try all sorts of different combinations. So with this, I'm going to build my point example with version 1 and I'm going to link it with version 1. In this case, they agree. So I see x and y being exactly what I expect in both cases. So we can actually, without recompiling anything because we're going to simulate a library update, we can do this. So this is a way to change environment variables on your machine. So there is an environment variable called LDPath, which will change the dynamic linker. You don't need to know this for the course. This is more for an interesting. This might come up later. And we'll see useful applications of it. But you don't need to know that for this course. But this is going to simulate a library update. So LDPath, this environment variable, will tell the operating system to look at this path first whenever you need to look up shared libraries. So if you wanted to, you could use this to overwrite the standard C library. If you wanted to write your own C library, you could set this and then run a application and it'll suddenly use your standard C library instead of the default one. So in this case, I'm telling it to first look for libraries in version 2 to simulate an update to version 2 of my code. And then otherwise, I just run the executable I ran before. So this sets an environment variable from whatever is to the right of it. So it will set this environment variable for this running this process. So if I run that this time, I'm now simulating me using version 2. And we can see that this one is reversed, which is not good. So that little change, if I was using this in my code, suddenly I update my library and then everything flips. So you could imagine how disorienting this would be if this was like a graphical application. And suddenly things are drawing nice in the x and the y. And then I flip x and y. So it just does a flip. And then suddenly everything is on its side and you're very, very confused. So that would not be a good bug to have, right? So any questions on that? There's some disagreement here, and that is not good. So should I explain it a bit more why that disagreement happened? OK, I got at least one nod. Yes. OK, so there I simulated it. So here's the explanation for it. So there are four boxes here to represent the executable and the library in gray. So on the left are the two versions of the executable. So for version one of the executable, my print line to the library, it just uses point get x and point get y. So it will call the library. Whatever function that actually calls depends on what library I use whenever I run it. So nothing is included in the executable itself. When I'm using the struct, though, well, it depends whatever version of the struct I use when I compiled it and created that executable is included in that executable. So if I compiled it with version one, x is at index one and y is at index zero. So remember, that was what we had here with our two versions. So for that line, it's kind of hard coded in the executable where x is and where y is. And then of course, if we create the executable with version two of the library, then the only difference is going to be the order of those struct fields because we changed them. So this would have the version two order of the struct fields. But otherwise, it would call the same library at runtime. And both executables also call the point create at runtime. So in the middle here is the functions that are in the library. In version one of the library, it would have those functions. And all those functions would be using this version or these indexes for the point struct. So in version one of the library, x would be at index one, y would be at index zero. And in version two of the library, same thing. So array x would be at zero and y would be at one. And then there's a question here. If hard coding is the source of this issue, why hard code, why not just account for them and code generally? So I didn't really hard code these. The compiler is doing it for me because it's saving me time. All I did is reorder. I only reordered the fields of a struct. So that's the only actual changes I made. Anything I'm explaining here is what actually goes on and what the compiler is actually doing. So we're not actually hard coding anything. The compiler is doing it for us. So in this case, so say if we used version one of the executable here with version one of the library, they're both going to agree. So when you create the point, x would be stored at index one, y would be stored at index zero. And when the executable calls point get x and point get y, well, it calls into the library and the library is going to agree with itself because it created the point and it would agree with itself. So there'd be no issues there. Now here the x and y hard coded in the executable agree with the library so we have no issue. They both agree. You'll get one and two. The problem is when we use version two of the library. So the struct this part won't change, but the library calls will change. So when you call point create, point create is going to use set x at index zero and y at index one. And point get x will use zero and get y will use one. So here the library version of it will agree with itself because we're using the library. So it will be correct. And then here they'll be swapped. They'll be disagreeing with each other. So this thinks x is at array index one, but really the library thinks it was y and it created the point to begin with. So we'll print two here instead of one and same thing with y. And then we'd also have disagreement. So if we had version two and version two, no problems. But if we mixed and match any, we'd also have problems. So if we had the version two of the executable with version one of the library, you'd have some disagreements and that would be bad. So going on the question discord, that is an interesting point of how to avoid this problem. So what's one way I could avoid this problem? Yeah, so one comment is, well, if I update my dynamic library, it can go find all the executables and fix them up. That fixing up is also called recompiling. And then it's the same thing as static library. Asking everyone to recompile genuinely isn't the same thing, but that would fix it. So you could just say, oopsies, I made a mistake. Please recompile everything. And in fact, this is exactly what happened with the standard C++ library not that long ago. They made one of these little boo-boos in standard list where they changed the order of the fields or added a field or did something like that. So suddenly, anything that used the standard C++ library didn't disagreed with the newest update. And the solution was we have to recompile literally everything. And that probably took about a year. So that's why that update took a long time to come into it because it broke something and everyone had to recompile. And everyone was very, very angry. But that is a solution if you want to make people angry. Any other solutions? Yep. Yeah, so I could just force them to use library functions for everything instead of directly accessing the struct. So I could take the struct out of the header file and only use it in the library and just never let them know about the definition of the struct and only internally use it in the library. So that means that you also have to use pointers, which is why pointers are so prevalent in C if you want to hide things. So they're not allowed to use the struct directly. I just don't let them look at it and then I can change it as much as I want. The drawback is they have to use pointers. So it'll be like Java where everything's a reference, but that is a good solution. The other solution would be if you give users a struct and now knowing this, well, if you give users a struct, suddenly you are not allowed to change it ever. You can add things to the end of it. That is okay because you're just making it bigger, but you are never allowed to change anything that already exists and you are not allowed to change the order of things. So whatever you create a struct, you have to be sure that it will exist for probably at least 50 years because you are now no longer allowed to change it. So the Linux developers are really serious about this. So they have like one rule not to break user space. So if they make a struct, they will argue about it for years because as soon as you let people use it, you can no longer change it. So you can either hide it or you're never allowed to change it ever again. So if you make one bad decision, that's too bad, you have to live with it. And yeah, we're being with not agreeing with the library as the comment is just the X and Y index don't agree between the library and whenever you compile your executable. So that's fun. So we can go through, if you compile this, there's a bunch of different program or a bunch of different examples. So you can mismatch libraries together and see all the difference. Basically, if they both agree, it'll work. If they disagree, it won't work. So if you use version one with library version two, it won't work. If you use version two of the executable with version one of the library, it won't work. They both have to be the same version. So there's this nice thing called semantic versioning that people should follow if they're writing libraries and it takes advantage of the version number. So normal version numbers are like 1.0.0 or something like that. So given a version number like that, you divide it into three. So it's major.minor.patch and the rules for those are, we increment the major version whenever you make some incompatible API or ABI change and that lets people know that, hey, if you're using version one of the library and you want to use version two, well, you can't just use it without recompiling your code. You're gonna have to recompile your code and fix it up to use this new version of the library. So that way, you can make changes that break things and you signal through the version number that they have to recompile it so you don't really piss anyone off. Then for the minor number, that just means you add functionality. So you add functions to it. So say in version, you have version 1.0, you create version 1.1 that adds a new function. Well, you increment that number and then people know that if they want to use that function, they should at least use version 1.1 of the library. So version 1.2 of the library would also include that function that's fine to use. And then incrementing the patch version, you just do that if you make a bug fix, you're not adding any functionality, you're just fixing things. So you should be able to use them interchangeably. So if I'm using 1.1.1, I should be able to update to 1.1.2 without any issues and it should work better because I fixed something without breaking something, hopefully. And if I broke that, I release 1.1.3 and fix that. So having these libraries also let you do some easier debugging. So you can use these environment variables to change the order of libraries that get used. And here's an example I'll use. So I will create this application or this program. All it's gonna do is malloc an int, size of an int, which should be four bytes. It's going to print the address of that int and then it's gonna free it because I should probably free my memory because I'm a good upstanding C programmer. Whoa, that's freaking out. And then I return and that's it. So this C program, how many mallocs do I call here? Wow, people are afraid to answer this. Yeah, way in the back. One, right? How many mallocs do I call one? This isn't a trick question. Yeah. Yeah, one, right? Program starts at main, calls malloc, does a printf, calls free, that's it. We can all read this, right? All right, so it uses one malloc. So I have written my own malloc. My own malloc does something a bit different. So my malloc will log any malloc and free calls that happen and then print them out on the terminal so we can track things. And basically this is how Valgrind works, kind of. So it will track them and make sure they match. So I'm going to force my library to run first. My malloc will do something different. So my malloc will do a printf and then call the real malloc. So yeah, I'll have to answer that question after. So here I will force my malloc to run and then see how many mallocs this actually runs. So how many mallocs happened? Two, how many did I write? One, what the hell is going on? Yeah, oh yeah, so here, so X is an int pointer and I cast it to a void star pointer just because this percent p just wants void pointers. Otherwise it'll give me a warning the compiler won't like me because it doesn't match up but it's not affecting anything. Yeah. Does printf use malloc? Does printf use malloc? Yes it does. So I called malloc once, that is still true. The other malloc actually came from printf. So also what happened? So I malloced, this is the address and I freed it. Why is this freaking out? And I freed it. They use malloc and they did what we teach you not to do. Did they free their memory? Why do they get to get away with it? That's terrible. So yeah, that's actually them using malloc and if we go to this, Valgrind, everyone's friend. So Valgrind's kind of useful for detecting memory leaks and anything you do bad with memory and we can go ahead and use it. So let's go ahead and use it and see what it says. So if we Valgrind our alloc example, well, it actually detects both allocations. So it says two allocations happened and two freeze happened. That's not true. We saw the freeze that happened, I never freed it, but says it did and that's the number of bytes actually used. So what the heck is going on here? Well, you get to read the fine print. So if you read the fine print of Valgrind, it says the GNU C library, so the standard C library which is used by all programs may allocate memory for its own uses. Usually it doesn't bother to free it because when the program ends, there would be no point since the Linux kernel reclaims all the processes resources when the process exits anyways, so it would just slow down. So they argue that they don't have to call free and then they hard-coded in Valgrind that if the standard C library allocates memory, they don't have to free it, it'll consider it free for you so it doesn't give you any false positives. But this doesn't excuse you from using free unfortunately unless you can make the same argument they do. So if you can make the same argument that I may use any memory I allocate until the program dies and whenever the program dies, the kernel will go ahead and free all the memory because that process isn't running anymore, it doesn't need memory. So the kernel will clean everything up for you, but you have to be using it until the process dies in order to excuse yourself from having to free memory. Otherwise, if there's memory being used that could have been freed, you're just wasting memory. So if you can argue that it should exist until the process exists, you don't have to free it, you're gonna have to make that argument to me if you don't call free. So be prepared for that. So another tool you can use if you don't like Valgrind is something called address sanitizer. So since we're using Meason, this is how you would use it in Meason and it compiles in checks for you. So it might run a bit better. So we can go ahead and run that. Oh, so to see that works, I'll just forget to free first. So if I forget to free and I compile it and then I run it, well, it will say I leak some memory, which is good. I leaked four bytes and it will actually say, hey, detected memory leaks, I leaked four bytes and it'll tell you exactly what allocation caused that leak. So here it says I leaked memory from line five, which is this line. So I should have freed this memory that came from this malloc. So you might find address sanitizer better. I like address sanitizer better than Valgrind, but hey, it's another tool in your arsenal. So another thing really quick. So system calls in C are really rare. You don't make system calls directly. What I did before was just kind of weird, but C has versions of system calls that pretty much map one to one. So they might have a corresponding C function, but C makes things a bit nicer. So C will set a global variable called error no, that explains any error that might happen with the system call if it doesn't complete successfully. It might also buffer reads and writes to reduce the number of system calls. So for instance, we know that printf calls write, but if you call, you might have noticed sometimes when you're using C, you use printf and you never see the result of that, even though you're like 100% sure your program made it past that line. Well, what the standard C library will do is buffer any calls to printf and it might not actually call write until it has enough data. So system calls are a bit slow. So if you called printf with one character 1,000 times, it wouldn't do a write system call until probably all 1,000 print or printf calls succeeded. And then it would do one system call that writes 1,000 bytes instead of doing 1,000 system calls that write one byte each, which will be much more efficient and much faster. They might simplify the interfaces combined two system calls together. They might add new features or things on top of the system calls just to make them easier to use. So for instance, in the last like two seconds, C exit doesn't call exit group. C has some additional features. So in C, you can actually register functions to run whenever it's starting to exiting whether it returns from main or you call exit. So in this case, I could define the PD function that runs no matter what whenever the program exits and will probably be out of time. So we'll actually execute this example Thursday. All right. So just remember, I'm pulling for you. We're all in this together.