With C-style I/O, it's easy to get the arguments to a std::printf mixed up, resulting in very strange results when you run the program. What's worse is that most compilers do not check std::printf calls and warn you if you make a mistake.
One special C I/O function you should be aware of is std::gets. This function gets a line from standard input with no bounds-checking. So:
std::fgets(line, INFINITY, stdin);
If there are too many characters in an input line, the std::gets function will cause a buffer overflow and trash memory. This single function and its lack of bounds-checking has to be responsible for more crashes and security holes than any other single C function. You should never use it. You can get in enough trouble with the more reliable C functions without having to play Russian roulette with this one.
I've done some benchmarks on C and C++ I/O for binary files. In general I've found the C I/O to be much faster. That's because the C I/O system is less flexible and has to deal with less overhead than the C++ system.
TIP: I'm not talking about formatted I/O, just raw binary I/O. If you do formatted I/O in either system, you can expect your speed to go down tremendously. It's the single slowest system in the entire C and C++ library.
Which Should You Use?
Which I/O system is best? That depends on a large number of factors. First of all, any system you know is always going to be easier to use and more reliable than a system you don't know.
However, if you know both systems, C-style I/O is good for the simple stuff. If you're not doing anything fancy with classes and just want to write simple formatted reports, the C I/O system will do the job. However, for larger jobs, the C++-object oriented system with its object-oriented I/O system handles complexity and organizes complex information much better than C-style I/O.
But if you're learning I/O for the first time, I suggest that you stick with one I/O system, the C++ one. Learn C-style I/O only if you're forced to. (Say, for instance, you have to maintain some legacy code that uses the old C-style system.)
Write a program that reads a file and counts the number of lines in it.
Write a program to copy a file, expanding all tabs to multiple spaces. (For historical reasons--the Teletype again--almost all text files use a tab setting of 8 characters.)
Write a program that reads a file containing a list of numbers and writes two files, one containing all the numbers divisible by 3 and another containing all the other numbers.
Write a program that reads an ASCII file containing a list of numbers and writes a binary file containing the same list. Write a program that goes the other way so you can check your work.
Write a program that copies a file and removes all characters with the high bit set (((ch & 0x80) != 0)).
Design a file format to store a person's name, address, and other information. Write a program to read this file and produce a file containing a set of mailing labels.
The problem is that you are writing an ASCII file, but you wanted a binary file. In Unix, ASCII is the same as binary, so the program runs fine. In MS-DOS/Windows, the end-of-line issue causes problems. When you write a newline character (0x0a) to the file, a carriage return (0x0D) is added to the file. (Remember that end-of-line in MS-DOS/Windows is <carriage return><line feed>, or 0x0d, 0x0a.) Because of this editing, you get an extra carriage return (0x0d) in the output file.
To write binary data (without output editing) you need to open the file with the binary option:
out_file.open("test.out", std::ios::out | std::ios::binary);
The std::printf call does not check for the correct number of parameters. The statement:
std::printf("The answer is %d\n");
tells the std::printf to print the string "The answer is" followed by the answer. The problem is that the parameter containing the answer was omitted. When this happens, std::printf gets the answer from a random location and prints garbage.
Properly written, the std::printf statement is:
std::printf("The answer is %d\n", answer);
The std::printf call does not check the type of its parameters. You tell std::printf to print an integer number (%d) and supply it with a floating-point parameter (result). This mismatch causes unexpected results, such as printing the wrong answer.
When printing a floating-point number, you need a %f conversion. Properly written, our std::printf statement is:
std::printf("The answer is %f\n", result);
The problem is that std::fgets gets the entire line, including the newline character (\n). If you have a file named sam, the program reads
sam\n and tries to look for a file by that name. Because there is no such file, the program reports an error.
The fix is to strip the newline character from the name:
name[strlen(name) - 1] = '\0'; /* Get rid of last character */
The error message in this case is poorly designed. True, you did not open the file, but the programmer could supply the user with more information. Are you trying to open the file for input or output? What is the name of the file you are trying to open? You don't even know whether the message you are getting is an error, a warning, or just part of the normal operation. A better error message is:
std::fprintf(stderr, "Error: Unable to open %s for input\n", name);
Notice that this message would also help us detect the programming error. When you typed in "sam", the error would be:
Error: Unable to open sam
This clearly shows us that you are trying to open a file with a newline in its name.
1. If you take a look at the C++ standard, you'll notice that the formal definition of these functions is somewhat more complex. I've simplified the definition for this book, but this definition is compatible with the formal one.
Practical C++ Programming, 2nd Edition
By Steve Oualline
O'Reilly & Associates, December 2002