C Fundamentals

Hello World in C Exercise

C Fundamentals

Lesson Description

The "Hello World in C Exercise" Lesson is part of the full, C Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:

Students are instructed to follow the steps in `exercises/1.c` and explore different use cases with the write and printf functions.

Preview
Close

Transcript from the "Hello World in C Exercise" Lesson

[00:00:00]
>> Richard Feldman: So all of these exercises are going to have the same exact format where, basically we have comments, we have a .c file, and then we have little comments that have a little arrow emoji. And then basically all you have to do for the exercise is just do what the comments say.

[00:00:12]
So I'm going to open up the first one here. This is a our first initial thing here exercise. So basically, at the very beginning of each of these exercises, we have, first, build and run the program. So make sure you're in the exercises directory, and then just run this.

[00:00:27]
So I am, this is my Zed code editor, if you haven't seen Zed before. It's awesome. It's written in Rust, so it is memory safe. [LAUGH] So basically, I'm just gonna cd into exercises. And now that I'm in exercises, if we want to just do a little LS, basically you've got exercises one through six.

[00:00:43]
So we have six different sections here, all of this, 1.c2, .C, etc. And when you compile them, which I'm going to do in a second here, with this command that you can just copy paste out of the exercise. It's going to actually generate a different little binary here, on your disk.

[00:00:56]
You can just delete those later if you want, but his is C. We compiled the binaries here. So when I paste this command and I say gcc-o app1, the -o means output. So this means output to app1. If you're on Windows, well, if you're on Windows, you should be using Windows Subsystem for Linux for this course.

[00:01:14]
But if you were doing Windows programming, you would actually say something app1.exe right here. Then you give it the name of the source file, which is o1.C, and then basically because I want to compile and then run. I'm telling GCC, which is the well technically GCC stands for the Goo C compiler, sorry, Goo Compiler Collection, yeah but it's used for C and C++.

[00:01:37]
It supports both. And actually on Mac OS, it's funny, this is actually not using GCC for real. It's just that GCC is so universal and so many build scripts rely on it, that actually Mac OS just aliases GCC to Clang, which is the actual compiler that Mac OS uses.

[00:01:53]
And the way you can tell this is if you say GCC dash version, it's, this is Clang, which is kind of same exact thing if you say clang-version. But if you're on Linux or Windows Subsystem for Linux, then it's actual real GCC. So if you do GCC-version, you're gonna see something different than what I see on this Mac here.

[00:02:11]
Anyway, so we're gonna build this and run it, and hey, it says hello world. Now I wanna call out your attention to something that it'll. It also says, there's this little weirdly formatted percent thing at the end here and this is actually part of the exercise. So the reason that that's happening is because we are printing out hello world without a new line at the end.

[00:02:29]
And a lot of shells will, if the program runs and the program does not end in a new line with its output to standard out, it prints this special character. And depending on what Shell you're using what terminal you're using, you might or might not see this, or you might see something slightly different.

[00:02:44]
But basically, this character is indicating hey, this program finished, and they wasn't a new line. But I'm going to put a new new line here anyway, because I don't want to put the prompt up here, because that'd look weird and actually, some shells will just put the prompt up here.

[00:02:56]
This part will just start right here, because it's the program didn't end with a new line. But it's also quite common for them to say, hey, just so you know I'm inserting this new line here. The program itself did not actually end in a new line. So that's one of the things we're gonna change is we're gonna change this string to HelloWorld\end.

[00:03:13]
So we're explicitly going to add in the new line. This is one of the things that Printf will do for you. So if you do printf, you don't actually need to put the new line there, printf will just automatically add the new line for you. But write will not, write is, I'm just gonna give you exactly this string.

[00:03:26]
I'm gonna send that straight to standard out, no questions asked. So we're gonna try changing that, and of course, when we do that, we do need to update the length here to 14. Because if you keep it at 13, it's only gonna print out the first 13 characters and it's Going to skip the new line, so that won't actually do anything unless you also change the length.

[00:03:42]
Then we're going to have some fun. So the next thing we want to do is try making the length less than 14. What happens when you run the program? You can probably guess it doesn't print the entire string. And now try making the length longer than 14. You're not going to get a warning, you're not going to get an error.

[00:03:55]
You're just going to see what happens. [LAUGH] Memory safety. Okay, so try uncommenting the next two lines as well as the include STDI at the top. So this is basically just switching it over from right to printf and we're gonna do a little bit of a actually interpolating a number in here.

[00:04:12]
And then once you've got that working, which is just uncommenting that and then uncommenting up here, then you wanna try adding a second number named num2. And pick some other number to do it and print out both of the numbers. So when you do that, hint, you will need to make two changes to printf's arguments.

[00:04:27]
You won't just need to change one of the arguments. And then finally, try having the program return something other than zero so you can just sort of see the exit code. So echo $?, is what you run in your terminal to see the output of your program. So after I do this, and then I do echo$?at print zero, because that is the exit code of the last program that I ran, which was the Hello World thing.

[00:04:52]
Okay, any questions about the exercises before we take some time. Yeah.
>> Speaker 2: So new line doesn't count as two characters. It's only one.
>> Richard Feldman: Yes, that's a great question. So \n, yes, is the way that we specify a new line character, that's two chracter of source code. But in terms of semantically what's actually happening in memory here, it's just one new line character.

[00:05:11]
So, yeah, what we're counting is semantic characters not source code characters, very good question, okay? Work through the exercises.
>> Richard Feldman: Great. All right, so let's go through the exercises here. We're gonna start by just doing the basic build and run the program again. So just to make sure that everything's working, hello world with the percent sign there.

[00:05:34]
So let's get rid of that little missing trailing new line there. So to do that I'm just gonna delete these comments as I go so we can remember what we've done. So change the string to hello world with a new line, and then change the length to 14.

[00:05:46]
Okay, great. And now we just don't see the percent sign anymore, because we actually have a new line there. Awesome. And now we're gonna make the length shorter, so we'll make it just 10, so that's HelloWorld and then once again, the percent sign is. Back, because we're not ending on a new line once again.

[00:06:00]
And now for the fun part, let's make it a lot longer. Boom, so we got hello world. And what is that stuff? What this is, is whatever happens to be in my binary after that string constant of Hello world, it's literally just spitting out whatever memory is in my executable.

[00:06:19]
That's what kind of memory safety we're dealing with. If there happened to be a secret there, an API key, it would heavily print that out, no problem. And if you were writing to a log or doing an HTTP response or something that, this could just happily send all of your API key secrets to the end user.

[00:06:34]
No problem. If you just got an integer wrong, if I bump it all the way up to 2000, we might see the same thing. Let's see. Can I get it long enough that. Yeah, look, we got something else. Look, there's something else, more there in memory. Now, a lot of these things are not printing anything.

[00:06:48]
That might just be because there's nothing printable there. A lot of these characters are not printable in the terminal. Just kind of silently mess that up. I don't know if it'll let me make be this long. Cool. Yeah, so now we've got lots of memory. Look at that.

[00:07:00]
This is all sorts of stuff that's in my binary, so, yeah. This is hello world program, really doing a good job dumping all the secrets. We can see, here's the name of our program. That's in there for some reason. BOF, I don't know what that is. There's are all just random 0s and 1s that are being interpreted by the terminal as being text, although, obviously a lot of these are not text Text.

[00:07:20]
They're just random 1s and 0s, gibberish, in some cases, white space, whatnot. Let's see if we can get the program to crash from doing this. It's kinda hard just happily spitting out all this memory. As you can see the level of memory safety we're doing, and we're going to see plenty more examples of this as we go.

[00:07:39]
But there was no compiler warnings here. There's no errors. There's no nothing. But if we did use printf, at least this particular error, we would not be susceptible to. This only happened because I was intentionally using a number other than 14 to demonstrate what happens if you get your math a little bit wrong when it comes to C.

[00:07:55]
And in the next section, we'll talk about a little bit more about what's going on under the hood there that makes this happen. Okay, so now let's get into the more ergonomic world. So if I just uncomment those two lines, int num=42, then it's going to be, hey, wait a minute.

[00:08:07]
Call the undeclared library function printf. What's going on with that? So, okay, I just need to uncomment that and now we are actually importing that because this unisted.h is where Write comes from and printf comes from stduio. So now if I print this, it's going to say, hello world, followed by the number is 42.

[00:08:27]
And if I want to print out a second number let's say we do you know num=2 I don't know 45. Then I can't just do this because if I do this, then it's gonna be like okay, well, didn't change anything. And I also can't do this, I can't just stick an extra num2 in there.

[00:08:42]
It's just going to give me a warning of, hey, wait a minute, data argument not used by format string. This is something about the compiler actually having some awareness of what printf is because it's part of the standard library. So if I run this, I will get a little warning here, but it's going to happily just do the same thing.

[00:08:58]
Basically the second argument is going can be totally ignored. The second number is %d. I don't need another newline there, whoops. Printf will do that for me. My bad, printf doesn't do that. I think you print line from Rust, my mistake. So yeah, second number is 45, great, and basically, if I want, I can change this to be something else, percent s for a string, and then it's going to complain wait a minute, wait a minute, you said this is an int, but it's not.

[00:09:28]
However, it'll still actually let me run it. It just might crash the program. This is a really fun error to get in your C code. Segmentation fault. You might notice the absence of a stack trace. You don't get one if you If you do a segmentation fault. Segmentation fault just basically means your program tried to read memory that it doesn't have permission to read according to the operating system.

[00:09:47]
So dead, end of program. [LAUGH] This is all you get. And debugging these is fun because this means that you have some error somewhere and you get no stack trace whatsoever by default. There are ways that you can try to wrangle your way into getting one, but usually the way that you would deal with something this is you have to fire up a debugger, hopefully you can reproduce it.

[00:10:07]
Sometimes these are highs and bugs that resist being reproduced. But yeah, you definitely don't ideally want to get segfaults if you can avoid them. So yeah, fortunately, this is actually one where the compiler warning would have actually helped us avoid that and told us, hey, this is the wrong type here.

[00:10:23]
But you do notice that, unlike in some other languages, this is just considered a warning in C. In most compilers, this would be a tight mismatch and it would be, yeah, no, this is an error. You're trying to use this thing as a string that's clearly not a string.

[00:10:34]
Bad things are gonna happen. C is, no, if you're sure, go ahead. You can feel free to run the program, it might segfault or do whatever. But yeah, even though it knows it's not going to block you, it's just going let you go ahead and do your thing.

[00:10:48]
Okay, moving on. Yeah, so we've had two different numbers and then finally, yeah, we can just return something other than 0, we can return 11. And now if I echo$?, it says 11. Any questions about the exercise?
>> Speaker 3: I have one that might be just pedantic with the language itself.

[00:11:08]
Is it worth anything to recast an intuit string? Can you do that? Are variables mutable in that way or is the juice not really worth the squeeze?
>> Richard Feldman: Can you recast a, okay, so the terminology here is important. I wouldn't say, well, okay, so certainly you can recast an integer to a string.

[00:11:28]
But what that would mean is, essentially, let's take this integer and let's pretend that the 1s and 0s in memory that represent that integer. We're just gonna reinterpret them and pretend that they're actually the 1s and 0s of a string. You can do that, and in fact, we actually are going to do that in this workshop, that's what C calls casting.

[00:11:44]
There's a separate thing you can do, which is converting, where you're actually gonna say, okay, I've got an integer and I want a string. Basically, you just call a function that says, turn this integer into a string, and it will do that for you. So we could do that, but honestly, idiomatically, I would just choose to use Print off to do that.

[00:11:59]
Because explicitly going out of your way to convert that is more work than necessary if this is just what all you're going to do with it anyway.
>> Speaker 3: Okay.
>> Richard Feldman: Yeah.
>> Speaker 4: When we include something at the top of the file importing it.
>> Richard Feldman: Yeah.
>> Speaker 4: In C, are we just including that entire library-

[00:12:14]
>> Richard Feldman: [LAUGH]
>> Speaker 4: Into our binary-.
>> Richard Feldman: Yeah
>> Speaker 4: Or can we pick just the right function?
>> Richard Feldman: So we're gonna talk about this more later, but literally, what pound include does is copy paste. So running this line of code is exactly the same thing as taking the contents of stdio.h, and pasting them into your file.

[00:12:34]
That's what pound include does. You might be wondering about, well, hang on a sec. What if I have,, that thing which depends on something else, and then I also depend on that something else? Is it going to get pasted multiple times? The answer is yes, but each of these files that I'm depending on does have a little bit of logic that uses different preprocessor macros to basically say.

[00:12:55]
Don't include myself semantically twice, even though the text is included twice. One of the things that the designers of Go wanted to do better based on their experiences with C, which some of them co-created. Was that they wanted to basically do a better job with imports and stuff that to make it more of a first class semantic thing so you can make some of the ergonomics around this better.

[00:13:16]
This is actually one of the reasons that C, especially C++ have worse compile times than they otherwise would. Is just, the semantics of how include work. I did want to mention, I think, in the previous question, I didn't address one implication of something you're asking, which was. What happens if there's 100 different functions in std.io, .h, but I'm only using one of them, what happens the other ones?

[00:13:38]
The C compiler actually doesn't deal with that. But basically there's a step after compilation called linking, which we're not really going to cover in this course because when I do GCC, it automatically takes care of doing that separately. But basically what linking does is it deals with after you've compiled your source code into the binary executable, there's this extra step.

[00:13:58]
That works on wiring in certain different dependencies and doing some other things. One of the things that can do is it can do an analysis of the different sections, you remember we had those labels LC0 and whatnot. There's also labels dot, main for the main function. And one of the things that can do is it can go look for, okay, do we ever actually jump into this section at all, anywhere ever?

[00:14:19]
If not just go ahead and delete it, so it'll just garbage collect those sections as long as things are organized into sections. So typically, that's how that sort of thing is done. Is, at the source code level, it is just copy pasted in the C compiler takes all of it, spits out a binary that's bigger than it needs to be.

[00:14:33]
And then later, the linker will garbage collect the different sections of the binary to get rid of the unused functions.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now