I have a logging function which works like this:
// public function:
void ConsoleUI::log(const std::string& format, ...) {
va_list args;
va_start(args, format);
log(format, args);
va_end(args);
}
// overloaded private function:
void ConsoleUI::log(const std::string& format, va_list args) {
//wprintw( outWin, format.c_str(), args);
//wprintw( outWin, "\n");
printf( format.c_str(), args);
printf( "\n");
}
(Side note: This should do the exact same thing as just a normal printf, so it's pretty useless in this state. That's because this is a minumal working example. In the end, this should work with ncurses - see commented section.)
I then create an instance of the ConsoleUI class, called ui:
ConsoleUI ui;
And later I use it to log stuff, more precisely, a time_duration in microseconds:
now = boost::posix_time::microsec_clock::local_time();
boost::posix_time::time_duration delta = now - lastTime;
double dt = 1e-6*(double)delta.total_microseconds();
lastTime = now;
ui.log( "logged dt: %f", dt );
ui.log( "logged dt 2: %lu", delta.total_microseconds());
printf( "dt: %f\n", dt);
printf( "dt 2: %lu\n", delta.total_microseconds());
The output I get:
logged dt: 0.000000
logged dt 2: 140736013247624
dt: 0.018739
dt 2: 18739
What I expected:
logged dt: 0.018739
logged dt 2: 18739
dt: 0.018739
dt 2: 18739
Note that over multiple calls of this, the values in the last two lines change slightly (as is to be expected of a delta time) and the first two values don't change - which looks like there's something wrong with the format.
So, bottom line: Calling printf directly works, but passing it down the logger and then calling printf doesn't work...
Since you can't actually pass your arguments from log to printf, you are passing a va_list - this is the right solution, but you can't pass a va_list to printf.
This line:
printf( format.c_str(), args);
prints args with the format in format. Given that args is of the type va_list (typically implemented in the comiler ABI as a void pointer) it will not represent the data that you expect in the format string. If you use
vprintf( format.c_str(), args);
it should work just fine, that's what vprintf is for.
(Note that it's nothing to do with "loss of precision" - it's passing incorrect type of argument to printf - and since it's a programmaticly formulated string, even enabling warnings is not going to work).
Related
I have the following functions:
void raiseError(const char *msg, ...)
{
va_list ap;
va_start(ap, msg); // use variable arg list
int size = vsnprintf(nullptr, 0, msg, ap);
std::vector<char> s(size+1, 0);
vsnprintf(s.data(), size, msg, ap);
va_end(ap);
errorString = std::string(s.data()));
}
When I call
raiseError("File not found in <%s> : <%s>", "a", "b" );
The first vsnprintf call (which computes the final string size) returns the correct value of 27. But the final string is:
"File not found in <**A*> :"
Where the '*' characters are random across program launches.
Also, the program works in MinGW, the issue is only seen with linux gcc.
What is wrong in my code?
You can't use ap more than once. Look into using va_copy.
I have the following log function:
template<typename... Arguments>
void Log(const char* file, const int line, int level, const char* fmt, Arguments... args)
{
std::string formattedFile;
if (file)
{
boost::filesystem::path p(file);
formattedFile = p.filename().string();
}
std::string message{boost::str(boost::format("%1%:%2% [%3%] - %s") % formattedFile % line % m_uxid % fmt)};
__android_log_print(level, m_tag.c_str(), message.c_str(), args...);
}
This application is run on Android using NDK, so this is the logging system for that platform. The problem is that __android_log_print() fails to compile with:
error: format not a string literal and no format arguments [-Werror=format-security]
__android_log_print(level, m_tag.c_str(), message.c_str(), std::forward<Arguments>(args)...);
^
I'm not sure what this means. Am I not using the variadic template argument correctly?
Untrusted input into printf can be a security problem. Enforcing the format by using a string literal is one way of improving security
Turning warnings into errors will cause the build to fail so you are forced to address the warning.
GCC's warning options have this to say
-Werror:
Make all warnings into errors.
-Wformat-security:
Warn about uses of format functions that represent possible security problems.
At present, this warns about calls to printf and scanf functions where the format string is not a string literal and there are no format arguments
This may be a security hole if the format string came from untrusted input and contains %n.
What is generally advised is to create a std::string in your function and pass this with a %s format string literal to your logging function
__android_log_print(level, m_tag.c_str(), "%s", message.c_str());
Where message is built from processing args..., typically using something like boost::format or a std::stringstream.
If you want to use your provided fmt string and the variadic args, you can parse the arguments using a custom printf style function which produces a std::string
std::string va_string_printf(const char* format, va_list ap)
{
char stack_buf[256];
char* buf = stack_buf;
int buf_size = sizeof(stack_buf);
std::string out_str;
while(true)
{
va_list ap1;
va_copy(ap1, ap);
int min_buf_size = vsnprintf(buf, buf_size, format, ap1) + 1;
va_end(ap1);
if (min_buf_size > buf_size)
{
if (buf != stack_buf) // allocate a bigger buffer
delete[] buf;
buf = new char[min_buf_size];
buf_size = min_buf_size;
continue;
}
out_str = buf;
break;
}
if (buf != stack_buf)
delete[] buf;
return out_str;
}
std::string string_printf(const char* format, ...)
{
va_list ap;
va_start(ap, format);
std::string str = va_string_printf(format, ap);
va_end(ap);
return str;
}
Why does my comportstr get changed in this code to a garbage value? I don't understand how my char array could change values if nothing about it is altered. There is nothing else between those two print statements.
int main(int argc, char* argv[])
{
char comportstr[10];
sprintf(comportstr,"COM%d",COMPORT_NUM);
if(DEBUGGING) fprintf(stderr,"str is: %s\n", comportstr); //prints str is: COM3
sprintf(curtime , "%s" , currentDateTime());
if(DEBUGGING) fprintf(stderr,"str is: %s\n", comportstr); //prints str is: ;32
}
Here's what currentDateTime does. It doesn't modify comportstr at all.
// Gets current date/time, format is YYYY-MM-DD.HH;mm;ss
const std::string currentDateTime()
{
time_t now = time(0);
struct tm tstruct;
char buf[80];
tstruct = *localtime(&now);
// Visit http://www.cplusplus.com/reference/clibrary/ctime/strftime/
// for more information about date/time format
strftime(buf, sizeof(buf), "%Y-%m-%d.%H;%M;%S", &tstruct);
return buf;
}
In your currentDateTime() function you are returning a std::string which is passed to sprintf()'s vararg interface. That doesn't work as you can't pass standard layout types to vararg interfaces. That is, the second call to sprintf() results in undefined behavior.
You can avoid the problem by using
sprintf(curtime , "%s" , currentDateTime().c_str());
... or, actually,
strcpy(curtime, currentDateTime().c_str());
sprintf(curtime , "%s" , currentDateTime());
The currentDateTime function returns a std::string, but %s is for C-style strings only. You want currentDateTime().c_str(). Your compiler should have given you a warning.
printf("%d.%d.%d", year, month, day);
Can I do the same but without printing, smth like
char* date = "%d.%d.%d", year, month, day;
Or maybe some other simple ways to do that?
In plain c there is asprintf() which will allocate memory to hold the resulting string:
#include <stdio.h>
char *date;
asprintf(&date, "%d.%d.%d", year, month, day);
(error handling omitted)
Since you have tagged C++ you probably want to use the C++ solutions.
In C++:
#include <string>
std::string date = std::to_string(year) + '.' +
std::to_string(month) + '.' + std::to_string(day);
If you need the underlying char const *, say date.c_str().
The function std::to_string uses snprintf internally; you should probably look up that function, too, as it is fairly fundamental to formatted output, and you can use it directly if you really think you need to.
There are various implementations of a format function that looks something like:
std::string format(const std::string& fmt, ...);
so your example would be:
std::string date = format("%d.%d.%d", year, month, day);
One possible implementation is shown below.
Boost has a format library that works a little differently. It assumes you like cin, cout, and their ilk:
cout << boost::format("%1%.%2%.%3%") % year % month % day;
Or, if you just wanted a string:
boost::format fmt("%1%.%2%.%3%");
fmt % year % month % day;
std::string date = fmt.str();
Note that % flags are not the ones you're used to.
Finally, if you want a C string (char*) instead of a C++ string, you could use the asprintf function:
char* date;
if(asprintf(&date, "%d.%d.%d", year, month, day) == -1)
{ /* couldn't make the string; format was bad or out of memory. */ }
You could even use vasprintf to make your own format function returning a C++ string:
std::string format(const char* fmt, ...)
{
char* result = 0;
va_list ap;
va_start(ap, fmt);
if(vasprintf(*result, fmt, ap) == -1)
throw std::bad_alloc();
va_end(ap);
std::string str_result(result);
free(result);
return str_result;
}
This isn't terribly efficient, but it works. There also might be a way to call vsnprintf twice, the first with no buffer to get the formatted string length, then allocate the string object with the right capacity, then call the second time to get the string. This avoids allocating the memory twice, but has to make two passes through the formatted string.
In C++ I wrote a function to create strings using the printf format.
Headerfile stringf.h:
#ifndef STRINGF_H
#define STRINGF_H
#include <string>
template< typename... argv >
std::string stringf( const char* format, argv... args ) {
const size_t SIZE = std::snprintf( NULL, 0, format, args... );
std::string output;
output.resize(SIZE+1);
std::snprintf( &(output[0]), SIZE+1, format, args... );
return std::move(output);
}
#endif
Usage:
#include "stringf.h"
int main(){
int year = 2020;
int month = 12;
int day = 20
std::string date = stringf("%d.%d.%d", year, month, day);
// date == "2020.12.20"
}
In C language use sprintf function from stdio.h header file.
char buffer[100];
sprintf(buffer,"%d.%d.%d", year, month, day);
See here for more info.
My product is targeted to a Portuguese audience where the comma is the decimal symbol. I usually use CString::Format to input numbers into strings, and it takes into account the computer's regional settings. While in general this is a good approach, I'm having problems in formatting SQL queries, for instance:
CString szInsert;
szInsert.Format("INSERT INTO Vertices (X, Y) VALUES (%f, %f)", pt.X, pt.Y);
When values are passed I get this string which is an incorrect query:
INSERT INTO Vertices (X, Y) VALUES (3,56, 4,67)
How do I enforce the dot as the decimal symbol in these strings, without changing the regional settings and without having to make specialized strings for each float value?
Note: this is intended as a general question, not a SQL one.
Bad idea, you really should be using prepared statements. It's not really trivial to do SQL injection with just numbers, but CString::Format is just not the correct way to do parameter binding.
(MFC and SQL has been a while - turns out this is bloody well hidden. I'm starting to see how we ended up with SQL injection bugs, thanks Microsoft. With raw ODBC you create a statement (once) with SQLPrepare. Pass ? for the 2 parameters you want to fill in. Subsequently, for each INSERT call SQLBindParameter(stmt, 1, &X); SQLBindParameter(stmt, 2, &Y) /*extra parameters omitted, see http://msdn.microsoft.com/en-us/library/ms710963(VS.85).aspx */. Finally, call SQLExecute to preform the operation. )
A comment about Pukku's suggestion with ostringstream: For this to be locale-independent, one should explicitely imbue() the stream with the desired locale:
std::ostringstream s;
s.imbue(std::locale::classic());
s << "INSERT INTO Vertices (X, Y) VALUES (" << pt.X << ", " << pt.Y << ")";
Otherwise, the current global locale is used.
Parameterized queries should avoid this issue altogether. You should look into those. That said, you should be able to use setlocale or similar to change the decimal separator.
Use
_create_locale( LC_NUMERIC, "C" )
to create an 'English' (C default) locale and then pass this to one of the _sprintf_l group of functions.
e.g.
_locale_t locale = _create_locale( LC_NUMERIC, "C" );
_sprintf_l( pszValue, "%f", locale, 3.141 );
_free_locale(locale);
This is thread-safe. The locale can be stored in a static variable to avoid creating it every time you need to format a value.
Here's what I did.
CString FormatQuery(LPCTSTR pszFormat, ...)
{
CString szLocale = setlocale(LC_NUMERIC, NULL);
setlocale(LC_NUMERIC, "English");
va_list args;
va_start(args, pszFormat);
CString szFormatted;
int nSize = (_vscprintf(pszFormat, args) + 1) * sizeof(char);
_vsnprintf_s(szFormatted.GetBuffer(nSize), nSize, nSize, pszFormat, args);
szFormatted.ReleaseBuffer();
va_end(args);
setlocale(LC_NUMERIC, szLocale);
return szFormatted;
}
You should use it like sprintf. You must #include <locale.h> in order for it to work.
I'm a bit stubborn so I didn't use prepared statements/parametrized queries. If you have a similar problem, I suggest you do that. Meanwhile, if your problem is not SQL-related, my answer should help.
Edit: Here's a thread safe version:
CString FormatQuery(LPCTSTR pszFormat, ...)
{
_locale_t locale = _create_locale(LC_NUMERIC, "English");
va_list args;
va_start(args, pszFormat);
CString szFormatted;
int nSize = (_vscprintf_l(pszFormat, locale, args) + 1) * sizeof(char);
_vsnprintf_s_l(szFormatted.GetBuffer(nSize), nSize, nSize, pszFormat, locale, args);
szFormatted.ReleaseBuffer();
va_end(args);
return szFormatted;
}