Skip to main content
gamedev.net
🔒 Locked

SIlly String

Started by CyberSlag5k Mar 27, 2007 at 3:50 PM 6 replies 2.5k views
Original Post
CyberSlag5k
CyberSlag5k
I've been away from C++ for a while, but I'm experiencing a problem I can't really explain:

// log.h
 
#ifndef LOG_H
#define LOG_H
 
#include<iostream>
#include<fstream>
#include<string>
#include<ctime>
 
using namespace std;
 
class Log
{
public:
	Log();
 
	void WriteLog(const string& message);
	void WriteLog(const string& header, const string& message);
private:
	string m_LogFileName;
 
};
 
#endif
 
// log.cpp
 
#include "log.h"
 
Log::Log()
{
	char date[9];
	_strdate_s(date);
	for(int i = 0; i < 9; i++)
		if(date == '/')
			date = '-';
 
	m_LogFileName = "Log.txt";
// REFERENCE LINE 1

//	m_LogFileName.append(date);
//	m_LogFileName.append(".txt");
}
 
void Log::WriteLog(const string& message)
{
	WriteLog("", message);
}
 
void Log::WriteLog(const string& header, const string& message)
{
	char date[9];
	char time[9];
	_strdate_s(date);
	_strtime_s(time);
 
	fstream logFile;
	logFile.open(m_LogFileName.c_str());
 
// REFERENCE LINE 2
	if(logFile.is_open())
	{
		logFile << "=========================================================================" << endl;
		logFile << date << " - " << time << endl;
		if(header != "")
			logFile << header << endl;
		logFile << message << endl;
		logFile << "=========================================================================" << endl;
	}
 
	logFile.close();

The above code is a fairly simple logging class I wrote in a few minutes. The problems I am experiencing revolve entirely around the m_LogFileName member. All I'm trying to do is to set it's value in the constructor. The log file isn't getting created, however, so I poked around a bit with the debugger to determine my problem. At REFERENCE LINE 1, the debugger tells me that m_LogFileName is a "". When it reaches REFERENCE Line 2, it has the same value as the "message" variable passed to the WriteLine method. The instance I'm using is a global variable declared as: Log logger; and used as: catch (exception e) { logger.WriteLog("General Exception", e.what()); } catch (...) { logger.WriteLog("General Exception", ""); } As you can see, this is some pretty simple stuff. Again, though, I've been hanging in C# land for a while, so perhaps I'm taking something for granted. Can anyone see what is wrong with this code? Thanks.
Without order nothing can exist - without chaos nothing can evolve.
Reckoner
Reckoner
Your code looked all right to me, but after digging around in the standard I've determined that the problem is the mode you are using to open the file.

According to table 92 in 27.8.1.3, opening a file with ios_base::in | ios_base::out (which is default for fstreams) is equivalent to using fopen with mode "r+". As mentioned in C language references, this mode will not create the file if it does not exist. To do that, you would use mode "w+". The C++ equivalent, according to the table, is ios_base::in | ios_base::out | ios_base::trunc. The trunc flag is needed to tell the function to create a new file if it does not exist.

Since you aren't reading the file, you can save yourself some trouble by simply using ios_base::out without ios_base::in like so:

logger.open(m_LogFileName.c_str(), std::ios_base::out);


For an even simpler solution, use a std::ofstream. You're not reading the file at all in this case, so you would be better off by directly specifying that you want an output stream.
Emmanuel Deloget
Emmanuel Deloget
1) I know it sounds like a dumb question, but aren't you debugging in release mode?
2) take care of this: "char date[9];" Are you sure the buffer is long enough to hold the date? "MM-DD-YYYY\0" is 11 char, not 9, so unless you are sure that you get 2 digits for the year, I suggest you to increase the size of the buffer.

@Reckoner: CyberSlag5k's problem deals with the string, not with the file :)

Best regards,
Reckoner
Reckoner
Quote:
Original post by Emmanuel Deloget
@Reckoner: CyberSlag5k's problem deals with the string, not with the file :)


Sorry, I should have read his post more closely to see that he mentioned having problems with the string. However, he also mentioned that the file wasn't being created, so that was what I looked at (and my reply was correct - the file won't be created using the mode that he is using). I did neglect to comment on his dangerous use of C strings, which you fortunately pointed out.

Regarding the actual string that's causing problems, I don't see why the debugger would say m_LogFileName is a "", considering that it is not a pointer at all. I compiled the code on my machine in VC++ 2005 Express Edition and it ran as expected, producing the desired log file; debugging showed no anomalies with that variable. I think more information is needed (such as more source code showing the actual use of the class, or more information about the OP's environment or setup) before I could go any further.
Zahlman
Zahlman
Quote:
Original post by Emmanuel Deloget
Are you sure the buffer is long enough to hold the date?


Motivated by this, I went to look up the function on MSDN. I think I have a Daily WTF candidate here ;)

Quote:
Teh MSDN
template <size_t size>errno_t _strdate_s(   char (&buffer)[size]); // C++ only



As usual, the _s suffix indicates that Microsoft has taken special care with the function design to avoid any buffer overflow.

But think about that a second. They took the care to take advantage of fairly poorly known C++ features so that you can pass in an array buffer and not worry about a size parameter. But as long as you're being C++y and also *offering a different function signature*, why not just use std::string, as nature intended? :s

But in fact, it gets worse:

Quote:

Error Conditions
numOfElements Return Contents of buffer
...
0 < size < 9 EINVAL Empty string
Size > 9 0 Current date formatted as specified in the remarks


The C++-friendly version of the function is templated on the array size (or takes a size parameter in C), which means we're working with a compile-time constant. It's perfectly possible in C++ to catch the error of a too-small buffer at compile-time, therefore, by simply doing template specialization. But apparently Microsoft doesn't care enough.

Oh well. Might as well make a wrapper...

std::string todayAsString() {  char buffer[9];  int err = _strdate_s(buffer);  assert (err == 0); // I shouldn't bother, but basically this checks whether MSDN is lying to me  return std::string(buffer);}


Of course, this still doesn't address the fact that you're totally on your own for getting the first two digits of the year. Oh, and here's another WTF:

Quote:

_wstrdate_s is a wide-character version of _strdate_s; the argument and return value of _wstrdate_s are wide-character strings. These functions behave identically otherwise.


They thought about providing a wide-character version, so you can do your i18n - except the date format is US-specific! (The internationally recognized standard is YYYY-MM-DD, which has several advantages while retaining the American preference for reading the month before the day.)
CyberSlag5k
CyberSlag5k
Thank you all for your responses. They were all informative, and have lead me to my problem(s).

First of all...

Quote:

1) I know it sounds like a dumb question, but aren't you debugging in release mode?


I've done some dumb things as a programmer (some of them intentional!) in my time, but this one has to be in the top 5. You absolutely called it. I was debugging in release mode. Gah! How on earth did you deduce that, BTW? Does it just make your checklist of "things to look at when the debugger is insane"? It definitely makes mine now.

Second...

Quote:

Since you aren't reading the file, you can save yourself some trouble by simply using ios_base::out without ios_base::in like so:


Great call there. I always use input and output streams as appropriate. That was simply a hard to spot typo (good eye!) and perfectly explains why the file wasn't getting created. Since I never got the file name right (until now), I never looked any farther than that.

And Zalhman, your response was interesting as always.

Thanks again all!
Without order nothing can exist - without chaos nothing can evolve.
Zahlman
Zahlman
Quote:
Original post by CyberSlag5k
Quote:

Since you aren't reading the file, you can save yourself some trouble by simply using ios_base::out without ios_base::in like so:


Also, don't call .open() on a default-constructed instance; .open exists so that you can play around with an existing stream, in the rare case that you need to. Otherwise, let the stream behave in a RAII way (more or less - you still have to check the state; a more "modern" design would have the stream's constructor throw an exception if something went wrong), by using the constructor accepting a filename parameter:

ofstream logger(m_LogFileName.c_str());


Why make it any harder? :)
Evil Steve
Evil Steve
Quote:
Original post by CyberSlag5k
I've done some dumb things as a programmer (some of them intentional!) in my time, but this one has to be in the top 5. You absolutely called it. I was debugging in release mode. Gah! How on earth did you deduce that, BTW? Does it just make your checklist of "things to look at when the debugger is insane"? It definitely makes mine now.
This is a prime example of what happens in release mode. The code seems to jump around, because the compiler generates code for lines in a different order for optimisation reasons, and as such, the variable might not be initialised on the line you think it is.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.