Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

g++ allows initialization of std::string to 0, but program crashes.

171 views
Skip to first unread message

Robbie Hatley

unread,
Mar 24, 2016, 1:22:01 PM3/24/16
to

I wrote a couple of functions in C++ to print out human-readable
local time and date strings (should be PDT in AM/PM format if
executed in my area) if given current time in time_t format from
time() in the <ctime> header. To test the functions I put them
in a test program (see below).

But the program failed to run, even though it compiled without warning,
with a whole mess of warnings turned on:

%make time-test.exe DEBUG=1 PEDANTIC=1
Using pattern rule %.exe:%.cpp to compile time-test.cpp to time-test.exe:
g++ -I /rhe/include -pedantic -Wall -Wextra -Wfloat-equal -Wshadow -Wcast-qual -Wcast-align -Wconversion -Winline
-Wcomments -Wundef -Wunused-macros -Wold-style-cast -Woverloaded-virtual -std=gnu++14 time-test.cpp -L/rhe/lib -L/lib
-lrh -lfl -ly -lm -lname -o /rhe/bin/test/time-test.exe
time-test.exe is up to date.

(No errors. No warnings.)

But at runtime, it fails abysmally. It gets to a certain point
and just quits, not even executing the rest of the program.
I expected it to write date and time strings, but the
execution crashed catastrophically about halfway through the
source code, without printing any error messages.

After hours of struggle, I *did* determine the problem:
I'd inadvertently initialized a std::string variable to 0,
instead of to "". But why did that even compile???
Why did g++ not scream about "illegal initialization of
a std::string object to an integer value" or some such???

The program that reproduces the bug begins as follows;
notice the line marked "OH, MY!!!":

#include <iostream>
#include <iomanip>
#include <string>
#include <ctime>

// Get local date from time_t time:
std::string GetDate
(
time_t Seconds1970, // seconds since 00:00:00UTC on Jan 1, 1970
int Length // date format: 0=micro, 1=short, 2=long
)
{
struct tm * TimeFields = NULL;
std::string DayOfWeek = "";
std::string Month = 0; // OH, MY!!!
int DayOfMonth = 0;
int Year = 0;
TimeFields = localtime(&Seconds1970);
Year = TimeFields->tm_year + 1900;
DayOfMonth = TimeFields->tm_mday;
...etc...
...etc...
...etc...


--
Cheers,
Robbie Hatley
Midway City, CA, USA
perl -le 'print "\154o\156e\167o\154f\100w\145ll\56c\157m"'
http://www.well.com/user/lonewolf/
https://www.facebook.com/robbie.hatley

Scott Lurndal

unread,
Mar 24, 2016, 2:55:04 PM3/24/16
to
Robbie Hatley <see.m...@for.my.address> writes:
>
>I wrote a couple of functions in C++ to print out human-readable
>local time and date strings (should be PDT in AM/PM format if
>executed in my area) if given current time in time_t format from
>time() in the <ctime> header. To test the functions I put them
>in a test program (see below).
>

Frankly, I'd just use 'strftime'.

char buf[MAXTIMESTRINGSIZE];
struct tm *tm = ::localtime(&Seconds1970);
size_t diag = ::strftime(buf, sizeof(buf), "%Y/%m/%d %h:%M %p %Z", tm);
if (diag != 0) {
return std::string(buf);
} else {
throw "Buffer overflow";
}

2016/03/24 11:52 AM PDT

http://pubs.opengroup.org/onlinepubs/9699919799/functions/strftime.html

Robbie Hatley

unread,
Mar 24, 2016, 6:05:15 PM3/24/16
to

On 3/24/2016 11:02 AM, Stefan Ram wrote:

> Robbie Hatley <see.m...@for.my.address> writes:
> > std::string Month = 0;
>
> I am not sure what is happening here. But it seems that 0 is
> used to initialize a string object via the constructor that
> accepts a char *.
>
> This would use
>
> string( char const * s );
>
> which requires 21.4.2p7, i.e.,
>
> Requires: s points to an array
>
> . One might now be able to say that »0« does not point to
> an array, in which case the execution of the initialization
> would have UB (17.6.4.11).

Ah, so that (sort of) explains why it even compiled. However,
while a compiler can't tell what the value of the input to a
parameterized constructor is going to be at runtime, in this
case the input is written as a literal, in which case it's known
at compile time. So unless the the author of a C++ compiler thinks
that 0 (aka the NULL pointer) can somehow point to a valid
C string, I don't see why the author wouldn't make the compiler
flag the following as being an error:

std::string s = 0; // This pointer points NOWHERE.
s = "banana"; // Let's crash the system!!! WHEEE!!!

> This in independent of what »g++« allows, since this is
> »comp.lang.c++«, not »comp.lang.g++«.

I don't see how what g++ allows (or what *any* C++ compiler allows)
can be independent of what C++ itself allows, if the authors of
such compilers wish to say "our c++ compiler is compliant with the
C++ standard". So, in my opinion, the authors of g++ screwed up
by not bothering to check for an extremely obvious error.

But on the other hand, the product costs $0.00 and comes with no
warranty:

%g++ --version
g++ (GCC) 5.3.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

So perhaps I should dive into the source code, come up with a fix for this
bug, and send to to FSF?

Robbie Hatley

unread,
Mar 24, 2016, 6:31:29 PM3/24/16
to

On 3/24/2016 11:54 AM, Scott Lurndal wrote:

> Robbie Hatley <see.m...@for.my.address> writes:
>>
>> I wrote a couple of functions in C++ to print out human-readable
>> local time and date strings (should be PDT in AM/PM format if
>> executed in my area) if given current time in time_t format from
>> time() in the <ctime> header. To test the functions I put them
>> in a test program (see below).
>>
>
> Frankly, I'd just use 'strftime'.
>
> char buf[MAXTIMESTRINGSIZE];
> struct tm *tm = ::localtime(&Seconds1970);
> size_t diag = ::strftime(buf, sizeof(buf), "%Y/%m/%d %h:%M %p %Z", tm);
> if (diag != 0) {
> return std::string(buf);
> } else {
> throw "Buffer overflow";
> }
>
> 2016/03/24 11:52 AM PDT

Firstly, my primary reason for posting here was to see why
g++ would allow a std::string to be initalized to 0. Hence the
title of my post.

As for your code, while I will look into strftime() to refresh
my knowledge of how it works, that's not the approach I took
for my time & date functions. I was more interested in getting
*away* from fixed-size manually-allocated arrays of char*,
and going to std::string as much as possible, especially since
these functions return std::string and will be in my personal
library and used by various C++ programs I write.

My implementation looks more like THIS:

====== BEGIN File /rhe/src/test/time-test.cpp ==================

#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <ctime>

using std::cin;
using std::cout;
using std::cerr;
using std::endl;
using std::setfill;
using std::setw;

// Get local date from time_t time:
std::string GetDate
(
time_t Seconds1970, // seconds since 00:00:00UTC on Jan 1, 1970
int Format // date format: 0=micro, 1=short, 2=long
)
{
struct tm * TP = NULL;
std::ostringstream OS = std::ostringstream ();

TP = localtime(&Seconds1970);
if (NULL == TP)
{
cerr << "ERROR in GetDate(): localtime returned NULL pointer." << endl;
exit(666);
}

if (0 == Format) // if user wants micro date format
{
OS << TP->tm_year + 1900 << "-"
<< setw(2) << setfill('0') << TP->tm_mon + 1 << "-"
<< setw(2) << setfill('0') << TP->tm_mday;
}
else if (1 == Format) // if user wants short date format
{
const char * ShortDays[7] =
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
const char * ShortMonths[12] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
OS << ShortDays[TP->tm_wday] << " "
<< ShortMonths[TP->tm_mon] << " "
<< setw(2) << setfill('0') << TP->tm_mday << ", "
<< TP->tm_year + 1900;
}
else // For any other value of Format, return full-length date:
{
const char * LongMonths[12] =
{"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"};
const char * LongDays[7] =
{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
OS << LongDays[TP->tm_wday] << " "
<< LongMonths[TP->tm_mon] << " "
<< setw(2) << setfill('0') << TP->tm_mday << ", "
<< TP->tm_year + 1900;
}
return OS.str();
}

// Get local time from time_t time:
std::string GetTime
(
time_t Seconds1970, // seconds since 00:00:00UTC on Jan 1, 1970
int Format // time format: 0=leading zero, 1=NO leading zero
)
{
struct tm * TP = NULL;
int Hour = 0;
std::string Meridien = std::string ();
std::ostringstream OS = std::ostringstream ();

TP = localtime(&Seconds1970);
if (NULL == TP)
{
cerr << "ERROR in GetTime(): localtime returned NULL pointer." << endl;
exit(666);
}

Hour = (TP->tm_hour + 11) % 12 + 1;
Meridien = (TP->tm_hour < 12) ? "AM" : "PM";
OS << setw(2) << setfill(0==Format ? '0' : ' ') << Hour << ":"
<< setw(2) << setfill('0') << TP->tm_min << ":"
<< setw(2) << setfill('0') << TP->tm_sec << Meridien;
return OS.str();
}

int main (void)
{
int i = 0;
time_t Time = 0;
std::string MicroDate = "";
std::string ShortDate = "";
std::string LongDate = "";
std::string TimeString = "";

Time = std::time(NULL);
for ( i = 0 ; i < 50 ; ++i )
{
MicroDate = GetDate(Time, 0);
ShortDate = GetDate(Time, 1);
LongDate = GetDate(Time, 2);
TimeString = GetTime(Time, 0);
cout << "MicroDate = " << MicroDate << endl;
cout << "ShortDate = " << ShortDate << endl;
cout << "LongDate = " << LongDate << endl;
cout << "TimeString = " << TimeString << endl;
Time += 3600;
cout << endl;
}
return 0;
}

====== END File /rhe/src/test/time-test.cpp ==================

Ian Collins

unread,
Mar 24, 2016, 7:08:38 PM3/24/16
to
This is one case where fixed-size arrays *are* the best fit!

--
Ian Collins

Robbie Hatley

unread,
Mar 24, 2016, 10:06:12 PM3/24/16
to

On 3/24/2016 4:08 PM, Ian Collins wrote:

> This is one case where fixed-size arrays *are* the best fit!

Perhaps. But, it's not very C++ish. "That's nice C code you
have there." Ultimately I suppose it's a style thing, since both
approaches (std::string + std::ostringstream, versus
char array[SIZE] + strftime) appear to work quite fine.
For the purpose of these two functions, I'm more comfortable
programming with the std:: strings & streams. But I'll admit
as how the "char array + strftime" approach is likely more
efficient because it's closer to the machine.

Ian Collins

unread,
Mar 24, 2016, 10:10:00 PM3/24/16
to
On 03/25/16 15:05, Robbie Hatley wrote:
>
> On 3/24/2016 4:08 PM, Ian Collins wrote:
>
>> This is one case where fixed-size arrays *are* the best fit!
>
> Perhaps. But, it's not very C++ish. "That's nice C code you
> have there." Ultimately I suppose it's a style thing, since both
> approaches (std::string + std::ostringstream, versus
> char array[SIZE] + strftime) appear to work quite fine.
> For the purpose of these two functions, I'm more comfortable
> programming with the std:: strings & streams. But I'll admit
> as how the "char array + strftime" approach is likely more
> efficient because it's closer to the machine.

It also save 90% of the typing!

--
Ian Collins

Robbie Hatley

unread,
Mar 25, 2016, 12:49:29 AM3/25/16
to

On 3/24/2016 11:02 AM, Stefan Ram wrote:

> This is independent of what »g++« allows, since this is
> »comp.lang.c++«, not »comp.lang.g++«.

You know, that does bring up a good point, in a way: different C++
compilers may handle this bug in different ways.

However, just about any C or C++ compiler I'm likely to be able to
get my hands on is going to be *some* variant or port of gcc / g++.

But let's try a minimal test program on 2 different compilers:

#include <iostream>
#include <string>
using std::cout;
using std::endl;
int main (void)
{
cout << "anaphylaxis" << endl;
std::string s = 0; // This pointer points NOWHERE.
cout << "brachiocephalic" << endl;
return 0;
}


Results on g++ v 5.0, Cygwin64 port, Windows10, Intel 64bit CPU:
[Compiles without error or warning.]
%string-init-to-0-test
anaphylaxis
[program crashes at that point and silently returns to Bash.]


Results on Dev-C++ 5.11 IDE with TDM-GCC compiler:
[Compiles without error or warning, but fails violently at runtime.]
anaphylaxis
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
Process exited after 210.3 seconds with return value 255
Press any key to continue . . .


Wow, it looks like multiple compilers (albeit all versions of gcc)
have this bug: they all silently accept initializing a std::string
to 0 at compile time, but the program always malfunctions at
runtime. I find that bizarre, and I really can't see a good reason
why compilers don't flag that construct as an "error" at compile-time.

Alain Ketterlin

unread,
Mar 25, 2016, 3:55:03 AM3/25/16
to
Robbie Hatley <see.m...@for.my.address> writes:

> #include <iostream>
> #include <string>
> using std::cout;
> using std::endl;
> int main (void)
> {
> cout << "anaphylaxis" << endl;
> std::string s = 0; // This pointer points NOWHERE.
> cout << "brachiocephalic" << endl;
> return 0;
> }

Trying with both g++-4.8.4 and g++-5.3.1 (on
http://www.tutorialspoint.com/compile_cpp11_online.php) I get this
message:

> terminate called after throwing an instance of 'std::logic_error'
> what(): basic_string::_S_construct null not valid

I don't know what smart terminal configuration you use that somehow
hides this message (or maybe it is make's), but it is a fact that the
program does exactly what it is supposed to do.

> Wow, it looks like multiple compilers (albeit all versions of gcc)
> have this bug:

The bug is in your own code, not in g++ nor in any compiler. The
standard (N4296) describes the constructor (21.4.2§8):

| basic_string(const charT* s, const Allocator& a = Allocator());
|
| Requires: s points to an array of at least traits::length(s) + 1
| elements of charT.

Your code violates the requirement. It "crashes" by throwing
logic_error, as expected.

> they all silently accept initializing a std::string to 0 at compile
> time, but the program always malfunctions at runtime. I find that
> bizarre, and I really can't see a good reason why compilers don't flag
> that construct as an "error" at compile-time.

Because <string> is a library, and you should not expect the compiler to
care about library-specific requirements, which are outside its scope.
You will get the same result by, e.g., calling at() on a map with an
invalid key, or by any other misuse of the library.

-- Alain.

Juha Nieminen

unread,
Mar 29, 2016, 2:26:49 AM3/29/16
to
Stefan Ram <r...@zedat.fu-berlin.de> wrote:
> So the compiler would need to include special code to check
> the English language preconditions of known functions in the
> special case that the compiler knows the values of the arguments.

What did you smoke before making that post?

--- news://freenews.netfront.net/ - complaints: ne...@netfront.net ---

Juha Nieminen

unread,
Mar 29, 2016, 2:30:59 AM3/29/16
to
Robbie Hatley <see.m...@for.my.address> wrote:
> On 3/24/2016 4:08 PM, Ian Collins wrote:
>
>> This is one case where fixed-size arrays *are* the best fit!
>
> Perhaps. But, it's not very C++ish.

Why not? C++ is intended to provide higher-level abstract object-oriented
programming capabilities without compromising efficiency. Fixed-size
arrays are one of the most efficient data structures (way, way more
efficient than any std::string or std::vector, unless in the former
case your string is short enough to allow for short string optimization,
assuming the library implementation does that, which isn't guaranteed.)

Static arrays are an integral part of C++, and personally I prefer
them if they easily solve the problem in hand and can be used safely.
I would never resort to a dynamic data container if a static array
would do just fine.
0 new messages