• No se han encontrado resultados

When you’ve figured out how it actually works, the final question is: Why would you ever want to do this? The purpose of this trick is to manually do loop unrolling. Large, long loops can be slow, so one way to speed them up is to find some fixed chunk of the loop, and then just duplicate the code in the loop that many times sequentially. For example, if you know a loop runs a minimum of 20 times, then you can put the contents of the loop 20 times in the source code.

Duff’s device is basically doing this automatically by chunking up the loop into eight iteration chunks. It’s clever and actually works, but these days a good compiler will do this for you. You shouldn’t need this except in the rare case where you have proven it would improve your speed.

Extra Credit

• Never use this again.

• Go look at the Wikipedia entry for Duff’s device and see if you can spot the error. Read the article, compare it to the version I have here, and try to understand why the Wikipedia code won’t work for you but worked for Tom Duff.

• Create a set of macros that lets you create any length of device like this. For example, what if you wanted to have 32 case statements and didn’t want to write out all of them? Can you do a macro that lays down eight at a time?

• Change the main to conduct some speed tests to see which one is really the fastest.

• Read about memcpy, memmove, and memset, and also compare their speed.

• Never use this again!

Exercise 24. Input, Output, Files

You’ve been using printf to print things, and that’s great and all, but you need more. In this

exercise, you’ll be using the functions fscanf and fgets to build information about a person in a structure. After this simple introduction about reading input, you’ll get a full list of the functions that C has for I/O. Some of these you’ve already seen and used, so this will be another memorization exercise.

ex24.c

Click he re to vie w code imag e 1 #include <stdio.h>

2 #include "dbg.h"

3

4 #define MAX_DATA 100 5

6 typedef enum EyeColor {

7 BLUE_EYES, GREEN_EYES, BROWN_EYES, 8 BLACK_EYES, OTHER_EYES

9 } EyeColor;

10

11 const char *EYE_COLOR_NAMES[] = {

12 "Blue", "Green", "Brown", "Black", "Other"

13 };

14

15 typedef struct Person { 16 int age;

17 char first_name[MAX_DATA];

18 char last_name[MAX_DATA];

19 EyeColor eyes;

20 float income;

21 } Person;

22

23 int main(int argc, char *argv[]) 24 {

25 Person you = {.age = 0 };

26 int i = 0;

27 char *in = NULL;

28

29 printf("What's your First Name? ");

30 in = fgets(you.first_name, MAX_DATA - 1, stdin);

31 check(in != NULL, "Failed to read first name.");

32

33 printf("What's your Last Name? ");

34 in = fgets(you.last_name, MAX_DATA - 1, stdin);

35 check(in != NULL, "Failed to read last name.");

36

37 printf("How old are you? ");

38 int rc = fscanf(stdin, "%d", &you.age);

39 check(rc > 0, "You have to enter a number.");

40

41 printf("What color are your eyes:\n");

42 for (i = 0; i <= OTHER_EYES; i++) {

43 printf("%d) %s\n", i + 1, EYE_COLOR_NAMES[i]);

44 }

45 printf("> ");

46

47 int eyes = -1;

48 rc = fscanf(stdin, "%d", &eyes);

49 check(rc > 0, "You have to enter a number.");

50

51 you.eyes = eyes - 1;

52 check(you.eyes <= OTHER_EYES

53 && you.eyes >= 0, "Do it right, that's not an option.");

54

55 printf("How much do you make an hour? ");

56 rc = fscanf(stdin, "%f", &you.income);

57 check(rc > 0, "Enter a floating point number.");

58

59 printf("--- RESULTS ---\n");

60

61 printf("First Name: %s", you.first_name);

62 printf("Last Name: %s", you.last_name);

63 printf("Age: %d\n", you.age);

64 printf("Eyes: %s\n", EYE_COLOR_NAMES[you.eyes]);

65 printf("Income: %f\n", you.income);

66

67 return 0;

68 error:

69

70 return -1;

71 }

This program is deceptively simple, and introduces a function called fscanf, which is the file scanf. The scanf family of functions are the inverse of the printf versions. Where printf printed out data based on a format, scanf reads (or scans) input based on a format.

There’s nothing original in the beginning of the file, so here’s what the main is doing in the program:

ex24.c:24-28 Sets up some variables we’ll need.

ex24.c:30-32 Gets your first name using the fgets function, which reads a string from the input (in this case stdin), but makes sure it doesn’t overflow the given buffer.

ex24.c:34-36 Same thing for you.last_name, again using fgets.

ex24.c:38-39 Uses fscanf to read an integer from stdin and put it into you.age. You can see that the same format string is used as printf to print an integer. You should also see that you have to give the address of you.age so that fscanf has a pointer to it and can modify it. This is a good example of using a pointer to a piece of data as an out parameter.

ex24.c:41-45 Prints out all of the options available for eye color, with a matching number that works with the EyeColor enum above.

ex24.c:47-50 Using fscanf again, gets a number for the you.eyes, but make sure the input is valid. This is important because someone can enter a value outside the EYE_COLOR_ NAMES array and cause a segmentation fault.

ex24.c:52-53 Gets how much you make as a float for the you.income.

ex24.c:55-61 Prints everything out so you can see if you have it right. Notice that

EYE_COLOR_NAMES is used to print out what the EyeColor enum is actually called.

What You Should See

When you run this program, you should see your inputs being properly converted. Make sure you try to give it bogus input too, so you can see how it protects against the input.

Exercise 24 Session

Click he re to vie w code imag e

$ make ex24

cc -Wall -g -DNDEBUG ex24.c -o ex24

$ ./ex24

What's your First Name? Zed What's your Last Name? Shaw How old are you? 37

What color are your eyes:

1) Blue 2) Green 3) Brown 4) Black 5) Other

> 1

How much do you make an hour? 1.2345

--- RESULTS ---First Name: Zed Last Name: Shaw Age: 37

Eyes: Blue

Income: 1.234500

How to Break It

This is all fine and good, but the really important part of this exercise is how scanf actually sucks.

It’s fine for a simple conversion of numbers, but fails for strings because it’s difficult to tell scanf how big a buffer is before you read it. There’s also a problem with the function gets (not fgets, the non-f version), which we avoided. That function has no idea how big the input buffer is at all and will just trash your program.

To demonstrate the problems with fscanf and strings, change the lines that use fgets so they are fscanf(stdin, "%50s", you.first_name), and then try to use it again. Notice it seems to read too much and then eat your enter key? This doesn’t do what you think it does, and rather than deal with weird scanf issues, you should just use fgets.

Next, change the fgets to use gets, then run your debugger on ex24. Do this inside:

"run << /dev/urandom"

This feeds random garbage into your program. This is called fuzzing your program, and it’s a good way to find input bugs. In this case, you’re feeding garbage from the /dev/urandom file (device), and then watching it crash. In some platforms, you may have to do this a few times, or even adjust the MAX_DATA define so it’s small enough.

The gets function is so bad that some platforms actually warn you when the program runs that you’re using gets. You should never use this function, ever.

Finally, take the input for you.eyes and remove the check that the number is within the right range.

Then, feed it bad numbers like -1 or 1000. Do this under the debugger so you can see what happens

there, too.

Documento similar