I'm trying to learn how to use file handling in C++.
I'd like to save and edit the contents of variables, however, there seems to be a problem with my logic because somehow the following program re writes the contents of the file infinitely.
#include <stdio.h>
#include <iostream>
int main(int argc, char const *argv[])
{
FILE *filePointer = fopen("text.txt", "r+");
char currentChar;
int loop = 0;
currentChar = getc(filePointer);
while (currentChar != EOF && loop < 100)
{
if (currentChar == '=')
{
fseek(filePointer, 1, 1);
if (fputs("LOL", filePointer) == EOF)
{
return 2;
}
}
std::cout << ftell(filePointer) << "Current Char: " << currentChar << std::endl;
currentChar = getc(filePointer);
loop++;
}
fclose(filePointer);
return 0;
}
The code that the program reads is the following:
"
hello = \n
yay!
"
This program exhibits undefined behavior. From C99 standard (which governs C standard library functions and is incorporated into the C++ standard by reference):
7.19.5.3/6 When a file is opened with update mode ('+' as the second or third character in the above list of mode argument values), both input and output may be performed on the associated stream. However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters end-of-file.
You have output (fputs) immediately followed by input (getc) without an intervening fflush or a file positioning function.
Text is stored contiguously on disk, and just like arrays, if you want to insert it, you must move the original element back, otherwise the effect of the execution is an overlay
Related
Below is a little program in C++ which is supposed to act as the cat linux binutil: it gets one or several inputs as detailed in the command line arguments (possibly specifying stdin via '-') and copy them onto the standard output. Unfortunately, it shows an unintended behaviour that I cannot understand the root causes of...
Upon the following command
./ccat - test.text
I hit CTRL-D directly without passing any character. I would expect the program to display anyway the content of test.txt, but instead, the program exits without passing any more characters onto the standard output stream.
Any idea on how I should correct my code below to have the correct behaviour in this situation? Should I handle multiple instances of the standard streams (cin, cout...)? If so, do you know how this can be achieved in C++?
Thank you in advance.
/**** ccat.cpp ****/
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int main(int argc, char **argv) {
if (argc <= 1) {
cout << cin.rdbuf();
} else {
vector<string> inputs;
for (int i=1; i<argc; ++i) inputs.push_back(argv[i]);
for (auto &in: inputs) {
if (in == "-" || in == "--") {
cout << cin.rdbuf();
}
else {
ifstream *fd = new ifstream(in);
if (!fd->is_open()) cerr << "Cannot open file \'" << in << "\'\n";
else cout << fd->rdbuf();
delete fd;
}
}
}
return 0;
}
I tried the following commands in sequence:
$ ./ccat > test.txt
Let's try this text.
I would expect a correct behaviour.
$ ./ccat - test.txt # I hit CTRL-D directly without passing any character first
$ ./ccat - test.txt
But when I add some characters before hitting CTRL-D... This works fine.
But when I add some characters before hitting CTRL-D... This works fine.
Let's try this text.
I would expect a correct behaviour.
As the example shows, I would expect in any of the two cases (last two shell prompts) that test.txt gets displayed onto the standard output, but this occurs only if I inject characters through the standard input first. Hitting CTRL-D straight away makes the program exit prematurely.
That's overload 10 here;
basic_ostream& operator<<( std::basic_streambuf<CharT, Traits>* sb );
and it says
If no characters were inserted, executes setstate(failbit).
In other words, cout is now in an error state and will not output anything.
Doing
cout.clear();
first of all in the else branch, or last of all in the if branch, should do it.
Note that sending end-of-file to standard input is usually not something you can recover or "restart" from, so you might only be able to use one standard input "section".
I've been having an issue with parsing through a file and the use of seekg(). Whenever a certain character is reached in a file, I want to loop until a condition is met. The loops works fine for the first iteration but, when it loops back, the file seemingly skips a character and causes the loop to not behave as expected.
Specifically, the loop works fine if it is all contained in one line in the file, but fails when there is at least one newline within the loop in the file.
I should mention I am working on this on Windows, and I feel like the issue arises from how Windows ends lines with \r\n.
Using seekg(-2, std::ios::cur) after looping back fixes the issue when the beginning loop condition is immediately followed by a newline, but does not work for a loop contained in the same line.
The code is structured by having an Interpreter class hold the file pointer and relevant variables, such as the current line and column. This class also has a functional map defined like so:
// Define function type for command map
typedef void (Interpreter::*function)(void);
// Map for all the commands
std::map<char, function> command_map = {
{'+', increment_cell},
{'-', decrement_cell},
{'>', increment_ptr},
{'<', decrement_ptr},
{'.', output},
{',', input},
{'[', begin_loop},
{']', end_loop},
{' ', next_col},
{'\n', next_line}
};
It iterates through each character, deciding if it has functionality or not in the following function:
// Iterating through the file
void Interpreter::run() {
char current_char;
if(!this->file.eof() && this->file.good()) {
while(this->file.get(current_char)) {
// Make sure character is functional command (ie not a comment)
if(this->command_map.find(current_char) != this->command_map.end()) {
// Print the current command if in debug mode
if(this->debug_mode && current_char != ' ' && current_char != '\n') {
std::cout << this->filename << ":" << this->line << ":"
<< this->column << ": " << current_char << std::endl;
}
// Execute the command
(this->*(command_map[current_char]))();
}
// If it is not a functional command, it is a comment. The rest of the line is ignored
else{
std::string temp_line = "";
std::getline(file, temp_line);
this->line++;
this->column = 0;
}
this->temp_pos = file.tellg();
this->column++;
}
}
else {
std::cout << "Unable to find file " << this->filename << "." << std::endl;
exit(1);
}
file.close();
}
The beginning of the loop (signaled by a '[' char) sets the beginning loop position to this->temp_pos:
void Interpreter::begin_loop() {
this->loop_begin_pointer = this->temp_pos;
this->loop_begin_line = this->line;
this->loop_begin_col = this->column;
this->run();
}
When the end of the loop (signaled by a ']' char) is reached, if the condition for ending the loop is not met, the file cursor position is set back to the beginning of the loop:
void Interpreter::end_loop() {
// If the cell's value is 0, we can end the loop
if(this->char_array[this->char_ptr] == 0) {
this->loop_begin_pointer = -1;
}
// Otherwise, go back to the beginning of the loop
if(this->loop_begin_pointer > -1){
this->file.seekg(this->loop_begin_pointer, std::ios::beg);
this->line = this->loop_begin_line;
this->column = this->loop_begin_col;
}
}
I was able to put in debugging information and can show stack traces for further clarity on the issue.
Stack trace with one line loop ( ++[->+<] ):
+ + [ - > + < ] [ - > + < ] done.
This works as intended.
Loop with multiple lines:
++[
-
>
+<]
Stack trace:
+ + [ - > + < ] > + < ] <- when it looped back, it "skipped" '[' and '-' characters.
This loops forever since the end condition is never met (ie the value of the first cell is never 0 since it never gets decremented).
Oddly enough, the following works:
++[
-
>+<]
It follows the same stack trace as the first example. This working and the last example not working is what has made this problem hard for me to solve.
Please let me know if more information is needed about how the program is supposed to work or its outputs. Sorry for the lengthy post, I just want to be as clear as possible.
Edit 1:
The class has the file object as std::ifstream file;.
In the constructor, it is opened with
this->file.open(filename), where filename is passed in as an argument.
For a file stream, seekg is ultimately defined in terms of fseek from the C standard library. The C standard has this to say:
7.21.9.2/4 For a text stream, either offset shall be zero, or offset shall be a value returned by an earlier successful call to the ftell function on a stream associated with the same file and whence shall be SEEK_SET.
So for a file opened in text mode, you can't do any arithmetic on offsets. You can rewind to the beginning, position at the end, or return to the position you were at previously and captured with tellg (which ultimately calls ftell). Anything else would exhibit undefined behavior.
I wrote the following C++ program to read a text file line by line and print out the content of the file line by line. I entered the name of the text file as the only command line argument into the command line.
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
char buf[255] = {};
if (argc != 2)
{
cout << "Invalid number of files." << endl;
return 1;
}
ifstream f(argv[1], ios::in | ios::binary);
if (!f)
{
cout << "Error: Cannot open file." << endl;
return 1;
}
while (!f.eof())
{
f.get(buf,255);
cout << buf << endl;
}
f.close();
return 0;
}
However, when I ran this code in Visual Studio, the Debug Console was completely blank. What's wrong with my code?
Apart from the errors mentioned in the comments, the program has a logical error because istream& istream::get(char* s, streamsize n) does not do what you (or I, until I debugged it) thought it does. Yes, it reads to the next newline; but it leaves the newline in the input!
The next time you call get(), it will see the newline immediately and return with an empty line in the buffer, for ever and ever.
The best way to fix this is to use the appropriate function, namely istream::getline() which extracts, but does not store the newline.
The EOF issue
is worth mentioning. The canonical way to read lines (if you want to write to a character buffer) is
while (f.getline(buf, bufSz))
{
cout << buf << "\n";
}
getline() returns a reference to the stream which in turn has a conversion function to bool, which makes it usable in a boolean expression like this. The conversion is true if input could be obtained. Interestingly, it may have encountered the end of file, and f.eof() would be true; but that alone does not make the stream convert to false. As long as it could extract at least one character it will convert to true, indicating that the last input operation made input available, and the loop will work as expected.
The next read after encountering EOF would then fail because no data could be extracted: After all, the read position is still at EOF. That is considered a read failure. The condition is wrong and the loop is exited, which was exactly the intent.
The buffer size issue
is worth mentioning, as well. The standard draft says in 30.7.4.3:
Characters are extracted and stored until one of the following occurs:
end-of-file occurs on the input sequence (in which case the function calls setstate(eofbit));
traits::eq(c, delim) for the next available input character c
(in which case the input character
is extracted but not stored);
n is less than one or n - 1 characters are stored
(in which case the function calls setstate(
failbit)).
The conditions are tested in that order, which means that if n-1 characters have been stored and the next character is a newline (the default delimiter), the input was successful (and the newline is extracted as well).
This means that if your file contains a single line 123 you can read that successfully with f.getline(buf, 4), but not a line 1234 (both may or may not be followed by a newline).
The line ending issue
Another complication here is that on Windows a file created with a typical editor will have a hidden carriage return before the newline, i.e. a line actually looks like "123\r\n" ("\r" and "\n" each being a single character with the values 13 and 10, respectively). Because you opened the file with the binary flag the program will see the carriage return; all lines will contain that "invisible" character, and the number of visible characters fitting in the buffer will be one shorter than one would assume.
The console issue ;-)
Oh, and your Console was not entirely empty; it's just that modern computers are too fast and the first line which was probably printed (it was in my case) scrolled away faster than anybody could switch windows. When I looked closely there was a cursor in the bottom left corner where the program was busy printing line after line of nothing ;-).
The conclusion
Debug your programs. It's very easy with VS.
Use getline(istream, string).
Use the return value of input functions (typically the stream)
as a boolean in a while loop: "As long as you can extract any input, use that input."
Beware of line ending issues.
Consider C I/O (printf, scanf) for anything non-trivial (I didn't discuss this in my answer but I think that's what many people do).
Here is a snippet from a c++ tutorial:
// istream::get example
#include <iostream> // std::cin, std::cout
#include <fstream> // std::ifstream
int main () {
char str[256];
std::cout << "Enter the name of an existing text file: ";
std::cin.get (str,256); // get c-string
std::ifstream is(str); // open file
while (is.good()) // loop while extraction from file is possible
{
char c = is.get(); // get character from file
if (is.good())
std::cout << c;
}
is.close(); // close file
return 0;
}
Notice is.good() appeared twice, first with while, then with if.
Link to the example: http://www.cplusplus.com/reference/istream/istream/get/
Why is the c++ input file stream checked twice here?
The fact of the matter is that it is unnecessarily checked twice. If the second inner if (is.good()) passes, then the outer while (is.good()) will always pass as well. The author of the code needed some way of looping, and he incorrectly assumed that a while (is.good()) is an appropriate condition because it will stop the loop when the stream fails to extract. But this is only half-true. while (is.good()) is never the correct way to perform the extraction.
You have to perform the input first and then check if it succeeded. Otherwise it is possible to perform a failed extraction, use the result of that extraction and receive unwanted behavior from your program. The correct way to do it is by using the extraction itself as the condition. The input operator will return a reference to the stream, and then it will turn into a boolean returning true if the previous read suceeded, or false otherwise:
while (is.get(c))
{
std::cout << c;
}
The variable c is also not outside of the loop. You can enclose the while() loop in a block or use a for() loop instead:
for (char c; is.get(c); )
{
std::cout << c;
}
But it seems that this code is attempting to write all the content from the file to standard output. Reading a character one-by-one is the way shown here, but you can also use stream iterators or the buffer overload of std::ostream::operator<<() as well.
There are two more problems I see in this code. Namely:
std::string is the preferred construct for manipulating dynamically-sized strings, not C-style strings which require the use of archaic input methods such as .get(), .getline(), etc, and their respective overloads.
Manually closing a file is usually unneeded. The stream will close itself at the end of the scope in which it was created. You probably only want to close the file yourself to check if it succeeds or to reopen the stream with a different file or openmode.
The first one, that in while (is.good()), checks if it has reached EOF (End Of File). If not, it doesn't enter the while loop. Once entered in while(), it means that it have at least one character remained for the instruction char c = is.get();.
What the second if() does is that it doesn't allow to print the last character read, because after a char c = is.get();, the file may reach EOF. In case it does, the character is not printed.
For example, let's say you have this file:
"Just an example!"
Now, if you had just:
while (is.good()) // loop while extraction from file is possible
{
char c = is.get(); // get character from file
std::cout << c;
}
the output would be: "Just an example! ". The last space is the EOF character (which is the last character read).
But with:
while (is.good()) // loop while extraction from file is possible
{
char c = is.get(); // get character from file
if (is.good())
std::cout << c;
}
the output would be: "Just an example!", which is what you would expect it to be.
Okay Some may remember me from earlier, I am fairly new to programming so I may seem not up to par with many others. However at the moment, i am very much stuck.
int main(int argc, char* argv[]) {
string temp,input,output;//store input from file, and get which file//
ofstream out("output.txt");
if(argc == 3)
{
if(ifstream(argv[2]))
{
input = argv[2];
ifstream in(input);
while(in.good())
{
in >> temp;
ReverseWord(temp);
cout << temp << endl;
out << temp << endl;
}
out.close();
in.close();
}
}
}
This code right here is meant to reverse the letter order of words that it takes in from a file by typing "revstr < input.txt" with input.txt being the file name. however at the moment the program just opens and closes right away without anything happening and nothing being typed into the console. does anyone know how to fix this?
If you call your program as revstr < input.text your main() function will be called (on usual platforms) with:
argv = { "revstr", NULL }
argc = 1
In this case you get the contents of input.txt by reading from std::cin. That is what 'input readirection' means: your standard input stream is redirected to read from a file rather than the keyboard (aka terminal) device. No need to deal with the filename in that case.
To pass a filename as argument, use revstr input.txt. That should call main()with
argv = { "revstr", "input.txt", NULL }
argc = 2
so the filename will be available as argv[1].
The behavior in the former case is typically due to command shells, which treat '<' as a redirection directive (which ends the preceding command). You may have expected to get
argv = { "revstr", "<", "input.txt", NULL }
argc = 3
For that you would need to apply some form of quoting or escaping to disable the shell behavior, for example revstr "<" input.txtor revstr \< input.txt. But as far as I understand where you are coming from, you want the redirection. In that case forget about argc and argv and simply read your input from std::cin.