Learn C The Hard Way (LCTHW) is a practical book teaching real world useful C .. Zed A. Shaw, you'll get a professional quality PDF and hours of HD Video. Of course I learned it the hard way. If you can learn C while sitting on broken glass, scorpions in your underwear, and with your hands on fire. Zed Shaw is a pseudoscientist who claims that copying code examples from his over priced book is the best way to learn a language. Nowhere.
|Language:||English, Spanish, Japanese|
|ePub File Size:||18.65 MB|
|PDF File Size:||18.12 MB|
|Distribution:||Free* [*Regsitration Required]|
Contribute to hatemBT/Template-learn-C-in-the-hard-way- development by creating an account on GitHub. Python is Learn Ruby The Hard also a great Way language. Learn Python The Hard Way Introduction: The Cartesian MAIN Dream Of C PREVIOUS Whatever I. Visit caite.info for a complete list of available publications. Learn C the hard way: practical exercises on the computational subjects you keep.
You play with your Ruby or Python and just make objects and variables without any care for where they live. Does the file ex1 exist already? Apress, You can write C code for Windows, that's not a problem. Try to see what the biggest database is before you cause the program to die for lack of memory from malloc. Lisa lived 2 years so far. This is standard C practice and it lets you ship binary libraries but still let the programmer compile against it.
Each chapter is Prentice Hall , Solutions to the problems are developed using the language C and the author's signature five-step problem solving process. Since learning O'Reilly, Author Richard Reese shows you how to use pointers Zed Shaw has perfected the world's best system for learning Python. Follow it and you will succeed-just like the hundreds of thousands of beginners Zed has taught to date!
You bring the discipline, commitment, and persistence; the author supplies everything else. In Learn Python Apress, Learn Intel 64 assembly language and architecture, become proficient in C, and understand how the programs are compiled and executed down to machine instructions, enabling you to write robust, high-performance code. Low-Level Programming explains Intel 64 architecture as the result of von Neumann architecture evolution. Unpack the archive to extract the files onto your computer.
Run make to make it build, just like you've been doing. Run sudo make install to install it onto your computer. Here's a script of me doing this very process, which I want you to try to replicate: If it doesn't build then try digging into why as well. Using Valgrind Using Valgrind is easy, you just run valgrind theprogram and it runs your program, then prints out all the errors your program made while it was running.
In this exercise we'll break down one of the error outputs and you can get an instant crash course in "Valgrind hell". Then we'll fix the program. First, here's a purposefully broken version of the ex3.
For practice, type it in again: This program is wrong on purpose. I've failed to initialize the height variable. I've forgot to give the first printf the age variable. What You Should See Now we will build this just like normal, but instead of running it directly, we'll run it with Valgrind see Source: For some reason the Debian or Ubuntu version of valgrind does this but not other versions.
This one is huge because Valgrind is telling you exactly where every problem in your program is. Starting at the top here's what you're reading, line by line line numbers are on the left so you can follow: Make sure the cc command you see is the same and has the -g option or your Valgrind output won't have line numbers.
That's where you forgot to include the age variable. You find this by looking at the error, then you see what's called a "stack trace" right under that. The line to look at first ex4. Typically it's the bottom most line that matters in this case, on line Valgrind hates this line.
This error says that some kind of if-statement or while-loop happened that was based on an uninitialized variable, in this case height. That is quite a lot of information to take in, but here's how you deal with it: Whenever you run your C code and get it working, rerun it under Valgrind to check it.
For each error that you get, go to the source: You may have to search online for the error message to figure out what it means. Once your program is "Valgrind pure" then it should be good, and you have probably learned something about how you write code. In this exercise I'm not expecting you to fully grasp Valgrind right away, but instead get it installed and learn how to use it real quick so we can apply it to all the later exercises.
Extra Credit Fix this program using Valgrind and the compiler as your guide. Read up on Valgrind on the internet. Download other software and build it by hand.
Try something you already use but never built for yourself. Look at how the Valgrind source files are laid out in the source directory and read its Makefile. Don't worry, none of that makes sense to me either. In this program you're going to type in a few more things that you're unfamiliar with, and I'm going to lightly break them HELP down. Then in the next few exercises we're going to work with these Follow concepts.
You probably won't but get in the habit of checking it. What You Should See This has pretty boring output, but the point of this exercise is to analyze the code: Let's break this down line-by-line quickly, and then we can do exercises to understand each part better: C has a convention of using.
How C programs work is the operating system loads your program, and then runs the function named main. Did that just fly over your head? Do not worry, we'll cover this soon.
In Python you just did a: In other languages you might have a begin or do word to start. In C statements except for logic end in a ';' semicolon character. Like in many languages function calls work with the syntax name arg1, arg2 ; and can have no arguments, or any number. The printf function is actually kind of weird and can take multiple arguments. We'll see that later. You may not be familiar with how Unix software uses return codes, so we'll cover that as well.
There's a lot of information in this break-down, so study it line-by-line and make sure you at least have a little grasp of what's going on. You won't know everything, but you can probably guess before we continue. Extra Credit For each line, write out the symbols you don't understand and see if you can guess what they mean. Write a little chart on paper with your guess that you can use to check later and see if you get it right.
Go back to the source code from the previous exercises and do a similar break-down to see if you're getting it. Write down what you don't know and can't explain to yourself. What You Should See Your output should look like mine, and you can start to see how the format strings for C are similar to Python and other languages. They've been around for a long time. You have 2. You have I have an initial A. I have a first name Zed. I have a last name Shaw. My whole name is Zed A. Here's the breakdown of how they match up: You'll notice that C makes a distinction between single-quote for char and double-quote for char or strings.
This is not valid C code, just a simpler way to talk about types when writing English. How To Break It You can easily break this program by passing the wrong thing to the printf statements. Make that change and the compiler will yell at you, then when you run it you might get a "Segmentation fault" like I did: Extra Credit Come up with other ways to break this C code by changing the printf , then fix them. Go search for "printf formats" and try using a few of the more exotic ones. Research how many different ways you can write a number.
Try octal, hexadecimal, and others you can find. Try printing an empty string that's just "". Adding 'l' the letter ell means "print this as a long decimal".
This is effectively the number 0. This demonstrates an ugly hack you find sometimes. This bit of source is entirely just an exercise, and demonstrates how some math works.
At the end, it also demonstrates something you see in C, but not in many other languages.
To C, a "character" is just an integer. It's a really small integer, but that's all it is. This means you can do math on them, and a lot of software does just that, for good or bad. This last bit is your first glance at how C gives you direct access to the machine. We'll be exploring that more in later exercises. What You Should See As usual, here's what you should see for the output: The entire universe has bugs.
You are expected to have That is only a 1. When you break it, run it under Valgrind to see what it says about your breaking attempts. What do these really huge numbers actually print out? Change long to unsigned long and try to find the number that makes that one too big. Go search online to find out what unsigned does. Try to explain to yourself before I do in the next exercise why you can multiply a char and an int.
C however treats strings as just NEXT arrays of bytes, and it's only the different printing functions that know there's a difference. Here's the code we'll be talking lzsthw about: Because arrays of data are so central to how C works, there's a huge number of ways to create them.
Look at the type, in this first case it's int. Look at the  and see that there's no length given. Create a piece of memory in the computer, that can hold 5 integers one after another. Take the name you want, areas and assign it this location. In the case of areas it's creating an array of 5 ints that contain those numbers.
The rest of the file, we're using a keyword called sizeof to ask C how big things are in bytes. C is all about the size and location of pieces of memory and what you do with them. To help you keep that straight, it gives you sizeof so you can ask how big something is before you work with it. This is where stuff gets tricky, so first let's run this and then explain further.
The size of a char: Your output could actually be totally different from mine, since your computer might have different size integers. I'll go through my output: Your computer might use a different size if it's a bit vs. Looking at the code, this matches what we put in the initializer. It says it's 4 bytes long, but we only typed "Zed" for 3 characters.
Where's the 4th one coming from? Make sure you can go through and see how these output lines match what was created. We'll be building on this and exploring more about arrays and storage next. How To Break It Breaking this program is fairly easy. Try some of these: Run it under Valgrind too.
Try running it under Valgrind a few times and see if you get some new errors. In some cases, you might still get lucky and not catch any errors. Change it so that instead of areas you try to print areas and see what Valgrind thinks of that. Try setting one element of areas to a character from name.
Go search online for the different sizes used for integers on different CPUs. In this exercise we'll more completely show the similarity between arrays and strings, and get into NEXT more about memory layouts. You probably clued into this Follow in the last exercise since we did it manually. Here's how we do it in lzsthw another way to make it even more clear by comparing it to an array of numbers: In numbers we are setting up numbers, but in name we're actually building a string manually.
What You Should See When you run this code you should see first the arrays printed with their contents initialized to zero, then in its initialized form: Z e d name: Zed another: Zed another each: I didn't have to give all 4 elements of the arrays to initialize them.
This is a short-cut that C has where, if you set just one element, it'll fill the rest in with 0. When each element of numbers is printed they all come out as 0. We then setup the arrays with a tedious manual assignment to each thing and print them out again. Look at how they changed.
Now the numbers are set, but see how the name string prints my name correctly? There's also two syntaxes for doing a string: The first one is less common and the second is what you should use for string literals like this. Notice that I'm using the same syntax and style of code to interact with both an array of integers and an array of characters, but that printf thinks that the name is just a string. Again, this is because to the C language there's no difference between a string and an array of characters.
This works out to be the same thing, but it's more idiomatic and easier to write. In fact it's so common and hard to get right that the majority of good C code just doesn't use C style strings.
In later exercises we'll actually learn how to avoid C strings completely. There's a few ways to do this: Get rid of the initializers that setup name.
Try to come up with some other ways to break this, and as usual run all of these under Valgrind so you can see exactly what is going on and what the errors are called. Sometimes you'll make these mistakes and even Valgrind can't find them, but try moving where you declare the variables to see if you get the error.
This is part of the voodoo of C, that sometimes just where the variable is located changes the bug. Extra Credit Assign the characters into numbers and then use printf to print them a character at a time. What kind of compiler warnings did you get? Do the inverse for name , trying to treat it like an array of int and print it out one int at a time. What does Valgrind think of that?
How many other ways can you print this out? If an array of characters is 4 bytes long, and an integer is 4 bytes long, then can you treat the whole name array like it's just an integer?
How might you accomplish this crazy hack? Take out a piece of paper and draw out each of these arrays as a row of boxes. Then do the operations you just did on paper to see if you get them right. Convert name to be in the style of another and see if the code keeps working.
The next thing is to NEXT take this one step further and do an array that has strings in it. We'll also introduce your first looping construct, the for-loop to help print out this HELP new data structure.
Here's code that will print out any command line arguments you pass it: The CODE runs, does whatever it does. This for-loop is going through the command line arguments using argc and argv like this: The OS passes each command line argument as a string in the argv array. The program's name. The OS also sets argc to the number of arguments in the argv array so you can process them without going past the end.
Remember that if you give one argument, the program's name is the first, so argc is 2. It then runs the code which just prints out the i and uses i to index into argv. What You Should See To play with this program you have to run it two ways. The first way is to pass in some command line arguments so that argc and argv get set.
California state 1: Oregon state 2: Washington state 3: The concept of multiple dimensions is something most people never think about so what you should do is build this array of strings on paper: Make a grid with the index of each string on the left. Then put the index of each character on the top. Then, fill in the squares in the middle with what single character goes in that cell.
Once you have the grid, trace through the code manually using this grid of paper. Another way to figure this is out is to build the same structure in a programming language you are more familiar with like Python or Ruby.
How To Break It Take your favorite other language, and use it to run this program, but with as many command line arguments as possible. See if you can bust it by giving it way too many arguments.
Initialize i to 0 and see what that does. Do you have to adjust argc as well or does it just work? Why does 0-based indexing work here? Extra Credit Figure out what kind of code you can put into the parts of a for-loop. Look up how to use the ',' comma character to separate multiple statements in the parts of the for-loop , but between the ';' semicolon characters.
Read what a NULL is and try to use it in one of the elements of the states array to see what it'll print. See if you can assign an element from the states array to the argv array before printing both.
Try the inverse. Let me explain NEXT something about it before we see how a while-loop works. HELP In C, there's not really a "boolean" type, and instead any integer that's 0 is Follow "false" and otherwise it's "true". This is another example of C being closer to how a computer works, because to a computer truth values are just integers. Now you'll take and implement the same program from the last exercise but use a while-loop instead.
This will let you compare the two so you can see how one is related to another. This means that to replicate how the for-loop works we need to do our own initializing and incrementing of i. What You Should See The output is basically the same, so I just did it a little different so you can see another way it runs.
Here's a few common ways: Forget to initialize the first int i; so have it loop wrong. Forget to initialize the second loop's i so that it retains the value from the end of the first loop. Now your second loop might or might not run. Extra Credit Make these loops count backward by using i-- to start at argc and count down to 0. You may have to do some math to make the array indexes work right. Use a while loop to copy the values from argv into states.
Make this copy loop never fail such that if there's too many argv elements it won't put them all into states. Research if you've really copied these strings. The answer may surprise and confuse you though.
Here's code that uses an if-statement to make sure you enter only 1 or 2 arguments: You suck. As mentioned before, the TEST parts are false if they evaluate to 0, and true otherwise. You have to put parenthesis around the TEST elements, while some other languages let you skip that. The braces make it clear where one branch of code begins and ends.
If you don't include it then obnoxious errors come up. Other than that, they work like others do. You don't need to have either else if or else parts. What You Should See This one is pretty simple to run and try out: Remove the else at the end and it won't catch the edge case.
Write a few more test cases for this program to see what you can come up with. Go back to Exercises 10 and 11, and use if-statements to make the loops exit early. You'll need the break statement to do that. Go read about it. Is the first test really saying the right thing? To you the "first argument" isn't the same first argument a user entered. Fix it. Some languages like Python just don't have a switch- statement since an if-statement with boolean expressions is about the NEXT same thing.
For these languages, switch-statements are more alternatives to if-statements and work the same internally. Instead of random boolean expressions, you can only put lzsthw expressions that result in integers, and these integers are used to calculate jumps from the top of the switch to the part that matches that value. Here's some code that we'll break down to understand this concept of "jump tables": You need one argument. Here's how the switch-statement works: The compiler marks the place in the program where the switch- statement starts, let's call this location Y.
It then evaluates the expression in switch letter to come up with a number. The compiler has also translated each of the case blocks like case 'A': Once it knows the location, the program "jumps" to that spot in the code, and then continues running. This is why you have break on some of the case blocks, but not others.
If 'a' is entered, then it jumps to case 'a' , there's no break so it "falls through" to the one right under it case 'A' which has code and a break. Finally it runs this code, hits the break then exits out of the switch-statement entirely. This is a deep dive into how the switch-statement works, but in practice you just have to remember a few simple rules: Always include a default: Always write the case and the break before you write the code that goes in it.
Try to just use if-statements instead if you can. What You Should See Here's an example of me playing with this, and also demonstrating various ways to pass the argument in: Z is not a vowel 1: S is not a vowel 5: Doing a return that's not 0 is how you indicate to the OS that the program had an error. Any value that's greater than 0 can be tested for in scripts and other programs to figure out what happened.
How To Break It It is incredibly easy to break a switch-statement. Here's just a few of the ways you can mess one of these up: Forget a break and it'll run two or more blocks of code you don't want it to run.
Forget a default and have it silently ignore values you forgot. Accidentally put in variable into the switch that evaluates to something unexpected, like an int that becomes weird values.
Use uninitialized values in the switch. You can also break this program in a few other ways. See if you can bust it yourself. Extra Credit Write another program that uses math on the letter to convert it to lowercase, and then remove all the extraneous uppercase letters in the switch.
Use the ',' comma to initialize letter in the for-loop. Make it handle all of the arguments you pass it with yet another for-loop. Convert this switch-statement to an if-statement. Which do you like better? In the case for 'Y' I have the break outside the if-statement. What's the impact of this and what happens if you move it inside the if-statement. Prove to yourself that you're right. In this exercise you will write some functions and use some other NEXT functions.
Here's the breakdown: This is a "forward declaration" and it solves the chicken-and-egg problem of needing to use a function before you've defined it. I shouldn't have to describe what's in each function because it's all things you've ran into before. What you should be able to see though is that I've simply defined functions the same way you've been defining main. The only difference is you have to help C out by telling it ahead of time if you're going to use functions it hasn't encountered yet in the file.
That's what the "forward declarations" at the top do. What You Should See To play with this program you just feed it different command line arguments, which get passed through your functions. Here's me playing with it to demonstrate: When I do the last run it prints everything but the '3' character, since that is a digit.
How To Break It There's two different kinds of "breaking" in this program: Extra Credit Rework these functions so that you have fewer functions. Use man to lookup information on isalpha and isblank. Use the other similar functions to print out only digits or other characters.
Go read about how different people like to format their functions. They NEXT actually aren't that complex, it's just they are frequently abused in weird ways that make them hard to use. If you avoid the stupid ways to use HELP pointers then they're fairly easy. Follow lzsthw To demonstrate pointers in a way we can talk about them, I've written a frivolous program that prints a group of people's ages in three different ways: As you go through this detailed description, try to answer the questions for yourself on a piece of paper, then see if what you guessed was going on matches my description of pointers later.
This is using i to index into the array. Seeing the similarity yet? Study that too, explain it to yourself. Why does that work? This seemingly simple program has a large amount of information, and the goal is to get you to attempt figuring pointers out for yourself before I explain them. Don't continue until you've written down what you think a pointer does. What You Should See After you run this program try to trace back each line printed out to the line in the code that produced it.
If you have to, alter the printf calls to make sure you got the right line number. Frank has 43 years alive. Mary has 12 years alive. John has 89 years alive. Lisa has 2 years alive. Frank is 43 years old. Mary is 12 years old. John is 89 years old. Lisa is 2 years old. Frank is 43 years old again. Mary is 12 years old again. John is 89 years old again.
Lisa is 2 years old again. Frank lived 43 years so far. Mary lived 12 years so far. John lived 89 years so far. Lisa lived 2 years so far.
If i is set to 0 then it's the same as typing ages. We've been calling this number i an "index" since it's a location inside ages that we want. It could also be called an "address", that's a way of saying "I want the integer in ages that is at address i ".
If i is an index, then what's ages? To C ages is a location in the computer's memory where all of these integers start.
It is also an address, and the C compiler will replace anywhere you type ages with the address of the very first integer in ages. Another way to think of ages is it's the "address of the first integer in ages". But, the trick is ages is an address inside the entire computer. It's not like i which was just an address inside ages. The ages array name is actually an address in the computer. That leads to a certain realization: C thinks your whole computer is one massive array of bytes.
Obviously this isn't very useful, but then C layers on top of this massive array of bytes the concept of types and sizes of those types. You already saw how this worked in previous exercises, but now you can start to get an idea that C is somehow doing the following with your arrays: Creating a block of memory inside your computer.
If you can take a base address, like ages , and then "add" to it with another address like i to produce a new address, then can you just make something that points right at this location all the time? Yes, and that thing is called a "pointer". They are variables pointing at the location where ages and names live in your computer's memory. The example program is then moving them around or doing math on them to get values out of the memory. In the last for-loop though these two pointers are being moved on their own, without i to help out.
In that loop, the pointers are treated like a combination of array and integer offset rolled into one. A pointer is simply an address pointing somewhere inside the computer's memory, with a type specifier so you get the right size of data with it. It is kind of like a combined ages and i rolled into one data type.
C knows where pointers are pointing, knows the data type they point at, the size of those types, and how to get the data for you. Just like i you can increment them, decrement them, subtract or add to them. But, just like ages you can also get values out with them, put new values in, and all the array operations.
The purpose of a pointer is to let you manually index into blocks or memory when an array won't do it right. In almost all other cases you actually want to use an array. But, there are times when you have to work with a raw block of memory and that's where a pointer comes in.
A pointer gives you raw, direct access to a block of memory so you can work with it. The final thing to grasp at this stage is that you can use either syntax for most array or pointer operations. You can take a pointer to something, but use the array syntax for accessing it. You can take an array and do pointer arithmetic with it.
Practical Pointer Usage There are four primary useful things you do with pointers in C code: Ask the OS for a chunk of memory and use a pointer to work with it. This includes strings and something you haven't seen yet, structs.
Passing large blocks of memory like large structs to functions with a pointer so you don't have to pass the whole thing to them. Taking the address of a function so you can use it as a dynamic callback. Complex scanning of chunks of memory such as converting bytes off a network socket into data structures or parsing files. For nearly everything else you see people use pointers, they should be using arrays. In the early days of C programming people used pointers to speed up their programs because the compilers were really bad at optimizing array usage.
These days the syntax to access an array vs.
Instead, you go with arrays every time you can, and then only use pointers as a performance optimization if you absolutely have to. The Pointer Lexicon I'm now going to give you a little lexicon to use for reading and writing pointers. Whenever you run into a complex pointer statement, just refer to this and break it down bit by bit or just don't use that code since it's probably not good code: Pointers Are Not Arrays No matter what, you should never think that pointers and arrays are the same thing.
They are not the same thing, even though C lets you work with them in many of the same ways. How To Break It You can break this program by simply pointing the pointers at the wrong things: You'll need to use a C cast to force it, so go look that up and try to figure it out.
In the final for-loop try getting the math wrong in weird ways. Try rewriting the loops so they start at the end of the arrays and go to the beginning. This is harder than it looks. Extra Credit Rewrite all the array usage in this program so that it's pointers. Rewrite all the pointer usage so they're arrays. Go back to some of the other programs that use arrays and try to use pointers instead.
Process command line arguments using just pointers similar to how you did names in this one. Play with combinations of getting the value of and the address of things. Add another for-loop at the end that prints out the addresses these pointers are using. Rewrite this program to use a function for each of the ways you're printing out things. Try to pass pointers to these functions so they work on the data. Remember you can declare a function to accept a pointer, but just use it like an array.
Change the for-loops to while-loops and see what works better for which kind of pointer usage. I'll NEXT also apply the knowledge of pointers from the last exercise and get you constructing these structures from raw memory using malloc. I'm not going to give you a line-by-line breakdown of the program, but I'm going to make you write it.
I'm going to give you a guide through the program based on the parts it contains, and your job is to write out what each line does. What does each give you?
The final result is a new compound type that lets me reference these elements all as one, or each piece by name. It's similar to a row of a database table or a class in an OOP language.
Here's the important things this function is doing: I use malloc for "memory allocate" to ask the OS to give me a piece of raw memory. I pass to malloc the sizeof struct Person which calculates the total size of the struct, given all the fields inside it. I use assert to make sure that I have a valid piece of memory back from malloc. There's a special constant called NULL that you use to mean "unset or invalid pointer".
This assert is basically checking that malloc didn't return a NULL invalid pointer. I use the strdup function to duplicate the string for the name, just to make sure that this structure actually owns it. The strdup actually is like malloc and it also copies the original string into the memory it creates. I again use assert to make sure I'm not getting bad input. Then I use the function free to return the memory I got with malloc and strdup.
If you don't do this you get a "memory leak". Create two people, joe and frank. Age both of them by 20 years, with changes to their body too. Print each one after aging them. Finally destroy the structures so we can clean up correctly. Go through this description carefully, and do the following: Look up every function and header file you don't know about.
Remember that you can usually do man 2 function or man 3 function and it'll tell you about it. You can also search online for the information. Write a comment above each and every single line saying what the line does in English.
Trace through each function call and variable so you know where it comes from in the program. Look up any symbols you don't know as well. What You Should See After you augment the program with your description comments, make sure it really runs and produces this output: Joe Alex Age: Frank Blank Age: A structure in C is a collection of other data types variables that are stored in one block of memory but let you access each variable independently by name.
They are similar to a record in a database table, or a very simplistic class in an object oriented language. We can break one down this way: In the above code, you make a struct that has the fields you'd expect for a person: Each of those fields has a type, like int.
C then packs those together so they can all be contained in one single struct. The struct Person is now a compound data type, which means you can now refer to struct Person in the same kinds of expressions you would other data types. There's also a way to make a struct that doesn't need a pointer, and you use the x. You'll do this in the Extra Credit. If you didn't have struct you'd need to figure out the size, packing, and location of pieces of memory with contents like this. In fact, in most early assembler code and even some now this is what you do.
With C you can let C handle the memory structuring of these compound data types and then focus on what you do with them. How To Break It With this program the ways to break it involve how you use the pointers and the malloc system: Figure out the options you need to pass to Valgrind to get it to print how you leaked this memory. Again, use the right options to see how Valgrind tells you exactly where you messed up. You should be figuring out that NULL is a quick way to crash your program.
Extra Credit In this exercise I want you to attempt something difficult for the extra credit: Convert this program to not use pointers and malloc. This will be hard, so you'll want to research the following: How to create a struct on the stack, which means just like you've been making any other variable.
How to initialize it using the x. How to pass a structure to other functions without using a pointer. This database isn't very NEXT efficient and doesn't store very much, but it does demonstrate most of what you've learned so far.
It also introduces memory allocation more HELP formally and gets you started working with files. As usual, type this whole program in and get it working, then we'll discuss: In it I'm using some things you've never seen, so you should go through it line-by-line, explain what each line does, and look up any functions you do not recognize.
There are few key things I'm doing that you should pay attention to as well: I'll cover more of what the CPP does, but this is a way to create a constant that will work reliably. There's other ways but they don't apply in certain situations. Fixed Sized Structs The Address struct then uses these constants to create a piece of data that is fixed in size making it less efficient, but easier to store and read.
The Database struct is then also fixed size because it is a fixed length array of Address structs. That lets you write the whole thing to disk in one move later on. I call this die , and it's used after any failed function calls or bad inputs to exit with an error using exit. These are just numbers, so you can use perror to "print the error message".
FILE functions I'm using all new functions like fopen , fread , fclose , and rewind to work with files. Each of these functions works on a FILE struct that's just like your structs, but it's defined by the C standard library. This trick makes sure that all fields but set and id are initialized to 0s and is actually easier to write.
Incidentally, you shouldn't be using memcpy to do these kinds of struct copying operations. Modern C allows you to simply assign one struct to another and it'll handle the copying for you. We'll get into better option parsing later in the book. Read up on this function and similar ones. I cover this in more detail below. On some rare systems NULL will be stored in the computer represented as something not 0, but the C standard says you should still be able to write code as if it has a 0 value.
What You Should See You should spend as much time as you can testing that it works, and running it with Valgrind to confirm you've got all the memory usage right. Here's a session of me testing it normally and then using Valgrind to check the operations: If you see it showing leaks that aren't inside your code then just ignore them.
Heap vs. Stack Allocation You kids these days have it great.
You play with your Ruby or Python and just make objects and variables without any care for where they live. You don't care if it's on the "stack", and the heap? You don't even know, and you know what, chances are your language of choice doesn't even put the variables on stack at all.
It's all heap, and you don't even know if it is. C is different because it's using the real CPU's actual machinery to do its work, and that involves a chunk of ram called the stack and another called the heap. What's the difference? It all depends on where you get the storage. The heap is easier to explain as it's just all the remaining memory in your computer, and you access it with the function malloc to get more. Each time you call malloc , the OS uses internal functions to register that piece of memory to you, and then returns a pointer to it.
When you're done with it, you use free to return it to the OS so that it can be used by other programs. Failing to do this will cause your program to "leak" memory, but Valgrind will help you track these leaks down. The stack is a special region of memory that stores temporary variables each function creates as locals to that function.
How it works is each argument to a function is "pushed" onto the stack, and then used inside the function. It is really a stack data structure, so the last thing in is the first thing out. This also happens with all local variables like char action and int id in main. The advantage of using a stack for this is simply that, when the function exits, the C compiler "pops" these variables off the stack to clean up.
This is simple and prevents memory leaks if the variable is on the stack. The easiest way to keep this straight is with this mantra: If you didn't get it from malloc or a function that got it from malloc , then it's on the stack. There's three primary problems with stacks and heaps to watch for: If you get a block of memory from malloc , and have that pointer on the stack, then when the function exits, the pointer will get popped off and lost.
If you put too much data on the stack like large structs and arrays then you can cause a "stack overflow" and the program will abort.
In this case, use the heap with malloc. If you take a pointer to something on the stack, and then pass that or return it from your function, then the function receiving it will "segmentation fault" segfault because the actual data will get popped off and disappear.
You'll be pointing at dead space. If you create a "create" function, that makes the whole thing or nothing, and then a "destroy" function that cleans up everything safely, then it's easier to keep it all straight. Finally, when a program exits the OS will clean up all the resources for you, but sometimes not immediately.
A common idiom and one I use in this exercise is to just abort and let the OS clean up on error. How To Break It This program has a lot of places you can break it, so try some of these but also come up with your own: The classic way is to remove some of the safety checks such that you can pass in arbitrary data.
For example, if you remove the check on line that prevents you from passing in any record number. You can also try corrupting the data file. Open it in any editor and change random bytes then close it.
You could also find ways to pass bad arguments to the program when it's run, such as getting the file and action backwards will make it create a file named after the action, then do an action based on the first character.
There is a bug in this program because of strncpy being poorly designed. Go read about strncpy then try to find out what happens when the name or address you give is greater than bytes. In the extra credit I have you augment the program to create arbitrary size databases. Try to see what the biggest database is before you cause the program to die for lack of memory from malloc. Extra Credit The die function needs to be augmented to let you pass the conn variable so it can close it and clean up.
Add more operations you can do on the database, like find. Read about how C does it's struct packing, and then try to see why your file is the size it is. See if you can calculate a new size after adding more fields. Add some more fields to the Address and make them searchable.
Use set -e at the top of a bash to make it abort the whole script if any command has an error. Try reworking the program to use a single global for the database connection. How does this new version of the program compare to the other one? Go research "stack data structure" and write one in your favorite language, then try to do it in C.
Just like you've been creating pointers to structs, strings, and arrays, you can point a pointer at a function too. The main NEXT use for this is to pass "callbacks" to other functions, or to simulate classes and objects. In this exercise we'll do some callbacks, and in the next one we'll make a simple object system. Write a normal function declaration: This is similar to how pointers to arrays can be used just like the arrays they point to.
Pointers to functions can be used like the functions they point to but with a different name.