26

I have string constants, for strings that I use in multiple places in my app:

namespace Common{
    static const std::string mystring = "IamAwesum";
}

When posting a question about something else (What happens to a .h file that is not included in a target during compilation?), another user made the following comment :

be aware that your static string are global in this case. So they are could create an exception at anytime and can't be catch. I advise you to use function who return a reference of your string. std::string const &mystring { static std::string const mystring = "IamAwesum"; return mystring} by this way your object is only construct when needed

Can someone explain why using static const strings in the manner that I do so above, risks throwing exceptions ?

Community
  • 1
  • 1
Rahul Iyer
  • 19,924
  • 21
  • 96
  • 190
  • something like this happened to me during the making of an embedded application. The dynamic memory allocation might not work yet. – user1095108 Nov 02 '16 at 14:13

3 Answers3

33

N4140 § 3.6.2 [basic.start.init]/ 4

It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main.

N4140 § N4140 15.3 [except.handle]/ 13

Exceptions thrown in destructors of objects with static storage duration or in constructors of namespace-scope objects with static storage duration are not caught by a function-try-block on main().

You simply cannot catch an exception generated by the string's constructor - say, std::bad_alloc.

(opinion) That being said, for such small strings I find this kind of consideration to be paranoid.

krzaq
  • 16,240
  • 4
  • 46
  • 61
  • Can you explain ? If it is not initialised before the first statement, then when will it be defined ? After the second ? Or can it be anytime in the future....? There are many lines of code in my program that execute before the strings are used..... – Rahul Iyer Nov 02 '16 at 10:29
  • 1
    @John, it could be done outside of `main` entirely. By the run-time environment. – StoryTeller - Unslander Monica Nov 02 '16 at 10:30
  • 1
    @John the paragraph follows with "If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first odr-use (3.2) of any function or variable defined in the same translation unit as the variable to be initialized." - that is, it must be initialized before it's accessed (but only in the same TU, where TU ≈ .cpp file) – krzaq Nov 02 '16 at 10:31
  • @StoryTeller - I don't follow.... so if many lines of code execute before the strings are needed what will happen... – Rahul Iyer Nov 02 '16 at 10:32
  • @krzaq I don't know what to make of that.... I have a separate "Strings" file, where I store all the strings as described above.... and many .hpp files include that file... and many lines of code execute before the first time I actually use one of those strings.... should I just not use a strings file then ? – Rahul Iyer Nov 02 '16 at 10:33
  • 2
    The problem is that the static could be initialized by the runtime before any of your code can "run". Meaning that, if your string is long enough and your memory small enough you might run into a `bad_alloc` without having run even 1 line of code in your own `main`, so there's no way to catch these exceptions. – Hatted Rooster Nov 02 '16 at 10:33
  • 6
    @John, there are basicly two models in which initialization code is inserted into your programs entry point by the compiler. (1) The compiler injects them into your `main` function definition (2) The compiler wraps your `main` function in another, where initialization occurs, and then your `main` is called. In the second case, you cannot catch an exception in your main function, since the code is executed a stack frame above it, so to speak. – StoryTeller - Unslander Monica Nov 02 '16 at 10:34
  • 2
    @John read the opinion part. Unless you're developing for embedded, I find it extremely unlikely that you'll have such a problem. – krzaq Nov 02 '16 at 10:34
  • @GillBates I imagine this could also happen if I have many many small strings ? – Rahul Iyer Nov 02 '16 at 10:37
  • 1
    @John If you run out of memory, yes.However as krzaq said, the chances of that happening are practically zero. – Hatted Rooster Nov 02 '16 at 10:38
  • 8
    Also, @krzaq even if this would happen I wonder what the downsides would be. It's not like catching a `bad_alloc` allows you to do stuff you wouldn't be able to do when not catching it since you can't guarantee anything at this point. – Hatted Rooster Nov 02 '16 at 10:39
  • So it seems that in the case of modern smartphones, which have 1-2GB of RAM, it is unlikely this will happen, but the only alternative, to be perfectly safe... is to return the string from a function..... But I think if we run out of memory on the smartphone we have much bigger problems (the app may not be able to run anyway)... so really on a platform like iOS, Android, or Windows Phone this isn't an issue.... – Rahul Iyer Nov 02 '16 at 10:40
  • @John you could imagine wrapping the app's execution in a handler which pre-allocates everything and catches all error. When an error occurs, it without creating more resources records the details, queues up a log to be sent, and maybe twiddles things (tries a previous version? resets settings?) to fix it for next launch, then tries to report the error. – Yakk - Adam Nevraumont Nov 02 '16 at 15:04
  • 1
    Regardless of when the static initialization code runs, it will not be caught by anything and will go straight to `std::terminate`. See [except.handle]/12; [basic.start.dynamic]/6. – T.C. Nov 02 '16 at 17:44
  • @T.C. thanks for the clarification. Edited. I can't find [basic.start.dynamic] though – krzaq Nov 02 '16 at 17:53
  • 1
    @John: you also have to consider whether the uncaught exception from the static initializer is any worse than your app failing to start because the dynamic loader can't allocate enough memory to get it loaded, or because the kernel can't allocate a stack for your main thread, or whatever. This depends on platform and what a call to `std::terminate()` actually looks like from the outside. If there's not enough memory to allocate the `std::string` instances then you're touch and go whether there's enough memory to get as far as calling `main()` even if you *weren't* doing that. – Steve Jessop Nov 02 '16 at 20:00
  • 2
    Of course on certain embeded systems all code is already in ROM, there's a limited number of processes and threads whose stacks are pre-allocated, limits on the size of modifiable static data areas, all that stuff, which might mean that program start-up is guaranteed, and furthermore the consequences of `std::terminate()` might be worse than you could achieve by catching the exception. Then sure, you want to make sure you catch the exception. If you're writing a Windows app, the system is already so screwed by the time that 10-byte allocation fails, nothing you do is likely to help... – Steve Jessop Nov 02 '16 at 20:08
  • @krzaq I was using the current WP. In N4140 it's [basic.start.init]/6. – T.C. Nov 03 '16 at 09:45
  • @T.C. Can you tell me what you're referring to ? What is basic.start.init, N4140, Wp ? basic.start.dynamic ? – Rahul Iyer Nov 04 '16 at 04:31
5

The only "issue" -- if you can call it that -- which I see with your code is that you are being wasteful by needlessly copying data that is already constant into a dynamically allocated buffer (which is formally constant, but not in reality). This uses twice as much physical memory as necessary and does a needless copy.

Does it matter? Almost certainly, no. Even on a "rather limited memory" system, this will nowadays hardly be noticeable, neither from an execution time point of view, nor by its memory consumption.

As for exceptions, it is of course technically true that the allocation that std::string has to make could fail, and therefore the constructor could throw, and you wouldn't be able to catch it. But please be realistic.
This is almost guaranteed not to happen, but even if it does... if something as trivial as allocating memory for a couple of string fails while your program starts up, you have a really, really serious issue on a completely different scale!
Besides, as pointed out in a comment on another answer above: Assuming this does happen, what are you going to do about it? The program is utterly unable to run, so there's not much short of killing the program that you could conceivably do.

Now, with C++17 not being far away and string_view already being available in std::experimental on several mainstream compilers, there's another thing you could try: Use the correct thing.

A string_view will, contrary to a string, not allocate non-constant memory, copy constant data into that, and then pretend it's constant. Instead, it will manage a pointer directly to the constant data, and that's all.
That way, your constants are truly (not just formally) constant, there are no allocations, no possibility of exceptions, and no double memory usage. And for the most part, it still looks and smells like a string. The only notable differences being that a string_view doesn't guarantee nul-termination (but the character constant it points to does, so this is irrelevant), and the fact that it's really constant, not modifiable... which is exactly what you want.

Damon
  • 67,688
  • 20
  • 135
  • 185
  • Using `string`s could be the right thing to do™ if he passes them to lots of functions taking string by const ref. Generally I agree with you, though. – krzaq Nov 02 '16 at 14:44
  • Beware that std::string might not be needed that often anymore. I am transitioning to string_view (if I remember the name correctly) for many of my functions, currently relying on boost::string_ref. Unless you need a 0-terminated string, string_view has many advantages over std::string const&.. – user3721426 Nov 02 '16 at 15:29
  • Is there an alternative? Is there any way to initialize a global string variable? – Edward Falk Jan 03 '22 at 19:03
4

The pdf document mostly refers to exceptions from the object ctor and initialization order fiasco with static or dynamically linked libraries.

The only danger I see in your code for exceptions is if the ctor of std::string will throw when it is called.

If you want to really be on the safe side you can use static const char* mystring instead which will not call to a C++ ctor.

There is also the matter of the code being in a shared library which then needs to be placed in the address space of the process. I don't see that as a major problem if you don't use complicated ctors(ctors that can throw).

Jonathan
  • 552
  • 1
  • 4
  • 10
  • Can't creation of a `const char*` result in allocation errors that you can't react to as well? – Jezor Dec 07 '17 at 02:51
  • 1
    If you use new or malloc then yes. If you use a string literal the compiler will store the string inside the executable and there won't be any C++ construction and destruction involved. – Jonathan Dec 07 '17 at 19:03
  • So the application won't even start if there's too little memory available? – Jezor Dec 07 '17 at 21:06
  • 1
    No, the application will start. This is where virtual memory kicks in. The operating system makes the app think it has all the machine memory for itself but in fact it swaps parts of the memory to the disk. When the app tries to access that memory the operating system detects that and loads the needed memory data back to the memory. – Jonathan Dec 08 '17 at 22:06