If you’ve done this right you should now see many of the different ways you can place variables in your C code. You can use extern or access functions like get_age to create globals. You can make new variables inside any blocks, and they’ll retain their own values until that block exits, leaving the outer variables alone. You also can pass a value to a function, and change the parameter but not change the caller’s version of it.
The most important thing to realize though is that all of this causes bugs. C’s ability to place things in many places in your machine and then let you access it in those places means you get confused easily about where something lives. If you don’t where it lives then there’s a chance you’ll not manage it properly.
With that in mind, here’s some rules to follow when writing C code so you avoid bugs related to the stack: 1. Do not "shadow" a variable like I’ve done here with count in scope_demo. It leaves you open to subtle and
118 CHAPTER 23. EXERCISE 22: THE STACK, SCOPE, AND GLOBALS
2. Avoid too many globals, especially if across multiple files. If you have to then use accessor functions like I’ve done with get_age. This doesn’t apply to constants, since those are read-only. I’m talking about variables like THE_SIZE. If you want people to modify or set this, then make accessor functions.
3. When in doubt, put it on the heap. Don’t rely on the semantics of the stack or specialized locations and instead just create things with malloc.
4. Don’t use function static variables like I did in update_ratio. They’re rarely useful and end up being a huge pain when you need to make your code concurrent in threads. They are also hard as hell to find compared to a well done global variable.
5. Avoid reusing function parameters as it’s confusing whether you’re just reusing it or if you think you’re changing the caller’s version of it.
As with all things, these rules can be broken when it’s practical. In fact, I guarantee you’ll run into code that breaks all of these rules and is perfectly fine. The constraints of different platforms makes it necessary some- times.
23.3
How To Break It
For this exercise, breaking the program involves trying to access or change things you can’t:
1. Try to directly access variables in ex22.c from ex22_main.c that you think you can’t. For example, you can’t get at ratio inside update_ratio? What if you had a pointer to it?
2. Ditch the extern declaration in ex22.h to see what you get for errors or warnings. 3. Add static or const specifiers to different variables and then try to change them.
23.4
Extra Credit
1. Research the concept of "pass by value" vs. "pass by reference". Write an example of both. 2. Use pointers to gain access to things you shouldn’t have access to.
3. Use valgrind to see what this kind of access looks like when you do it wrong.
4. Write a recursive function that causes a stack overflow. Don’t know what a recursive function is? Try calling scope_demo at the bottom of scope_demo itself so that it loops.
Chapter 24
Exercise 23: Meet Duff ’s Device
This exercise is a brain teaser where I introduce you to one of the most famous hacks in C called "Duff ’s Device", named after Tom Duff the "inventor". This little slice of awesome (evil?) has nearly everything you’ve been learning wrapped in one tiny little package. Figuring out how it works is also a good fun puzzle.
Note 6 This Is Only An Exercise
Part of the fun of C is that you can come up with crazy hacks like this, but this is also what makes C annoying to use. It’s good to learn about these tricks because it gives you a deeper understanding of the language and your computer. But, you should never use this. Always strive for easy to read code.
Duff ’s device was "discovered" (created?) by Tom Duff and is a trick with the C compiler that actually shouldn’t work. I won’t tell you what it does yet since this is meant to be a puzzle for you to ponder and try to solve. You are to get this code running and then try to figure out what it does, and why it does it this way.
ex23.c
1 #include <stdio.h> 2 #include <string.h> 3 #include "dbg.h" 4 56 int normal_copy(char *from, char *to, int count) 7 {
8 int i = 0; 9
10 for(i = 0; i < count; i++) { 11 to[i] = from[i]; 12 } 13 14 return i; 15 } 16
17 int duffs_device(char *from, char *to, int count) 18 {
19 {
20 int n = (count + 7) / 8;
21
22 switch(count % 8) {
23 case 0: do { *to++ = *from++;
24 case 7: *to++ = *from++;
25 case 6: *to++ = *from++;
120 CHAPTER 24. EXERCISE 23: MEET DUFF’S DEVICE
26 case 5: *to++ = *from++;
27 case 4: *to++ = *from++;
28 case 3: *to++ = *from++;
29 case 2: *to++ = *from++;
30 case 1: *to++ = *from++;
31 } while(--n > 0); 32 } 33 } 34 35 return count; 36 } 37
38 int zeds_device(char *from, char *to, int count) 39 { 40 { 41 int n = (count + 7) / 8; 42 43 switch(count % 8) { 44 case 0:
45 again: *to++ = *from++;
46
47 case 7: *to++ = *from++; 48 case 6: *to++ = *from++; 49 case 5: *to++ = *from++; 50 case 4: *to++ = *from++; 51 case 3: *to++ = *from++; 52 case 2: *to++ = *from++; 53 case 1: *to++ = *from++;
54 if(--n > 0) goto again;
55 } 56 } 57 58 return count; 59 } 60
61 int valid_copy(char *data, int count, char expects) 62 {
63 int i = 0;
64 for(i = 0; i < count; i++) { 65 if(data[i] != expects) {
66 log_err("[%d] %c != %c", i, data[i], expects);
67 return 0; 68 } 69 } 70 71 return 1; 72 } 73 74
75 int main(int argc, char *argv[]) 76 {
77 char from[1000] = {'a'}; 78 char to[1000] = {'c'}; 79 int rc = 0;
80
24.1. WHAT YOU SHOULD SEE 121
82 memset(from, 'x', 1000); 83 // set it to a failure mode 84 memset(to, 'y', 1000);
85 check(valid_copy(to, 1000, 'y'), "Not initialized right."); 86
87 // use normal copy to
88 rc = normal_copy(from, to, 1000);
89 check(rc == 1000, "Normal copy failed: %d", rc);
90 check(valid_copy(to, 1000, 'x'), "Normal copy failed."); 91 92 // reset 93 memset(to, 'y', 1000); 94 95 // duffs version 96 rc = duffs_device(from, to, 1000);
97 check(rc == 1000, "Duff's device failed: %d", rc);
98 check(valid_copy(to, 1000, 'x'), "Duff's device failed copy."); 99 100 // reset 101 memset(to, 'y', 1000); 102 103 // my version 104 rc = zeds_device(from, to, 1000);
105 check(rc == 1000, "Zed's device failed: %d", rc);
106 check(valid_copy(to, 1000, 'x'), "Zed's device failed copy."); 107
108 return 0; 109 error:
110 return 1; 111 }
In this code I have three versions of a copy function:
normal_copy Which is just a plain for-loop that copies characters from one array to another.
duffs_device This is the brain teaser called "Duff ’s Device", named after Tom Duff, the person to blame for this delicious evil.
zeds_device A version of "Duff ’s Device" that just uses a goto so you can get a clue about what’s happening with the weird do-while placement in duffs_device.
Study these three functions before continuing. Try to explain what’s going on to yourself before continuing.
24.1
What You Should See
There’s no output from this program, it just runs and exits. You should run it under valgrind and make sure there are no errors.