{"id":112738,"date":"2026-09-28T07:00:00","date_gmt":"2026-09-28T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112738"},"modified":"2026-09-28T20:11:26","modified_gmt":"2026-09-29T03:11:26","slug":"20260928-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260928-00\/?p=112738\/","title":{"rendered":"C++ reminder: Function-local static variables are initialized only once, even if it looks like they get initialized multiple times"},"content":{"rendered":"<p>When you write a static variable inside a function, it is initialized only once, specifically <a title=\"Magic statics vs. std::call_once\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260916-00\/?p=112703\"> at the first time that execution reaches the variable&#8217;s declaration<\/a>, If execution reaches the variable again in the future, no initialization occurs. It just retains its old value.<\/p>\n<p>Some time ago, I noted an attempt to fix a lifetime issue by making a variable static.<\/p>\n<p>The original code used this header from an external widget library:<\/p>\n<pre>\/\/ widget.h\r\nstruct WidgetController\r\n{\r\n    virtual WidgetKind GetKind() = 0;\r\n    virtual WidgetFlags GetFlags() = 0;\r\n    virtual void OnOpening() = 0;\r\n    \u27e6 and so on \u27e7\r\n};\r\n\r\nstd::shared_ptr&lt;Widget&gt;\r\n    MakeWidget(std::shared_ptr&lt;WidgetController&gt; const&amp; controller);\r\n<\/pre>\n<p>The idea is that you give it a <code>Widget\u00adController<\/code> object that the widget consults at various times, allowing you to customize the widget behavior.<\/p>\n<p>The application code called it like this:<\/p>\n<pre>\/\/ The basic Widget controller provides information but\r\n\/\/ does not override any default behaviors.\r\n\r\nstruct BasicWidgetInfo\r\n{\r\n    WidgetKind kind;\r\n    WidgetFlags flags;\r\n    \u27e6 and so on \u27e7\r\n};\r\n\r\nstruct BasicWidgetController : WidgetController\r\n{\r\n    BasicWidgetController(BasicWidgetInfo const&amp; info) :\r\n        m_info(info) {}\r\n\r\n    WidgetKind GetKind() override { return m_info.kind; }\r\n    WidgetFlags GetFlags() override { return m_info.flags; }\r\n\r\n    \/\/ Do not customize any dynamic actions.\r\n    void OnOpening() override { }\r\n    \u27e6 and so on \u27e7\r\n\r\nprivate:\r\n    BasicWidgetInfo const&amp; m_info;\r\n}\r\n\r\nstruct Gadget\r\n{\r\n    std::shared_ptr&lt;Widget&gt; m_widget;\r\n\r\n    void CreateWidget(GadgetFlags flags)\r\n    {\r\n        BasicWidgetInfo info = {\r\n            WidgetKind::Vanilla,\r\n            WidgetFlags::Openable |\r\n            (flags &amp; GadgetFlags::ClosableWidget ?\r\n                WidgetFlags::Closable : WidgetFlags::None)\r\n        };\r\n\r\n        auto controller = std::make_shared&lt;BasicWidgetController&gt;(info);\r\n\r\n        m_widget = MakeWidget(controller);\r\n    }\r\n\r\n    \u27e6 other gadget stuff \u27e7\r\n};\r\n<\/pre>\n<p>The catch is that the <code>Widget\u00adOptions<\/code> constructor takes a reference to a <code>Gadget\u00adOptions<\/code> and saves the reference. Later, when the widget asks the controller for the flags, the controller will look up the answer in the <code>BasicWidgetInfo<\/code> structure, but that <code>BasicWidgetInfo<\/code> had already destructed when <code>Create\u00adCustom\u00adWidget<\/code> returned, so it returns garbage (or possibly even crashes).<\/p>\n<p>This is a use-after-free bug.<\/p>\n<p>To solve this problem, they made the <code>info<\/code> static. Static objects continue to exist even after the function returns.<\/p>\n<pre>    void CreateWidget(GadgetFlags flags)\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">static<\/span> BasicWidgetInfo info = {\r\n            WidgetKind::Vanilla,\r\n            WidgetFlags::Openable |\r\n            (flags &amp; GadgetFlags::ClosableWidget ?\r\n                WidgetFlags::Closable : WidgetFlags::None)\r\n        };\r\n\r\n        auto controller = std::make_shared&lt;BasicWidgetController&gt;(info);\r\n\r\n        m_widget = MakeWidget(controller);\r\n    }\r\n<\/pre>\n<p>Now the program doesn&#8217;t crash. Yay!<\/p>\n<p>However, there is a catch: If two Gadgets both try to create a widget, all of them will have the same options as the first one, because function-local static variables are shared among all instances of a class and are initialized only the first time execution reaches the variable. Whatever flags were passed when you called it the first time get locked into the <code>info<\/code>, and it doesn&#8217;t matter what flags you pass subsequent times because <code>info<\/code> has already been initialized; it&#8217;s not going to initialize again.<\/p>\n<p>If you want it to initialize each time, then you have to modify it each time.<\/p>\n<pre>    void CreateWidget(GadgetFlags flags)\r\n    {\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">static BasicWidgetInfo info;<\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">info = {                    <\/span>\r\n            WidgetKind::Vanilla,\r\n            WidgetFlags::Openable |\r\n            (flags &amp; GadgetFlags::ClosableWidget ?\r\n                WidgetFlags::Closable : WidgetFlags::None)\r\n        };\r\n\r\n        auto controller = std::make_shared&lt;BasicWidgetController&gt;(info);\r\n\r\n        m_widget = MakeWidget(controller);\r\n    }\r\n<\/pre>\n<p>This time, we set the values as a step separate from construction, which means that it executes each time, and the <code>info<\/code> gets updated with the most recent flags.<\/p>\n<p>Of course, this is still a problem if two Gadgets create Widgets with overlapping lifetime, because the two <code>Basic\u00adWidget\u00adController<\/code>s are sharing the same <code>info<\/code>. At the second call to <code>Create\u00adWidget<\/code>, its updates to <code>info<\/code> secretly alter the values being used by the first one.<\/p>\n<p>Plus, of course, if <code>Create\u00adWidget<\/code> is called by two threads simultaneously, you have a data race on the writes to the <code>info<\/code> variable, and then the results will be unpredictable.<\/p>\n<p>The underlying problem is that the <code>Basic\u00adWidget\u00adController<\/code> wants to extend the lifetime of its <code>info<\/code>, but a reference gives you no way to do it, so it has to rely on the kindness of strangers.<\/p>\n<p>One idea would be to put the <code>info<\/code> somewhere else, so that its lifetime can be extended some other way. Maybe you put it in the <code>Gadget<\/code>:<\/p>\n<pre>struct Gadget\r\n{\r\n    std::shared_ptr&lt;Widget&gt; m_widget;\r\n    <span style=\"border: solid 1px currentcolor;\">BasicWidgetInfo m_info;<\/span>\r\n\r\n    void CreateWidget(GadgetFlags flags)\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">m_info = {<\/span>\r\n            WidgetKind::Vanilla,\r\n            WidgetFlags::Openable |\r\n            (flags &amp; GadgetFlags::ClosableWidget ?\r\n                WidgetFlags::Closable : WidgetFlags::None)\r\n        };\r\n\r\n        auto controller = std::make_shared&lt;BasicWidgetController&gt;(<span style=\"border: solid 1px currentcolor;\">m_info<\/span>);\r\n\r\n        m_widget = MakeWidget(controller);\r\n    }\r\n\r\n    \u27e6 other gadget stuff \u27e7\r\n};\r\n<\/pre>\n<p>Now your job is to make sure that the <code>m_info<\/code> is not destructed before the last shared pointer to the <code>Basic\u00adWidget\u00adController<\/code>. This is tricky, since you don&#8217;t really know when the last shared pointer to the <code>Basic\u00adWidget\u00adController<\/code> will be destructed, although you might have some heuristics given that its lifetime is probably tied to the <code>Widget<\/code>.<\/p>\n<p>Is there a way to hook into the destruction of the final <code>shared_ptr<\/code>?<\/p>\n<p>Yes, and in fact we already used that feature without realizing it.<\/p>\n<p>You can use an aliasing shared pointer that points at a <code>Basic\u00adWidget\u00adController<\/code> but whose lifetime controls both a <code>Basic\u00adWidget\u00adController<\/code> and its associated <code>Basic\u00adWidget\u00adInfo<\/code>.<\/p>\n<pre>struct BasicWidgetControllerWithInfo\r\n{\r\n    BasicWidgetControllerWithInfo(BasicWidgetInfo const&amp; info) :\r\n        m_info(info),\r\n        m_controller(m_info) {}\r\n\r\n    \/\/ The m_info must come before the m_controller because the\r\n    \/\/ m_controller initializer depends on the m_info.\r\n    BasicWidgetInfo m_info;\r\n    BasicWidgetController m_controller;\r\n};\r\n\r\n    void CreateWidget(GadgetFlags flags)\r\n    {\r\n        BasicWidgetInfo info = {\r\n            WidgetKind::Vanilla,\r\n            WidgetFlags::Openable |\r\n            (flags &amp; GadgetFlags::ClosableWidget ?\r\n                WidgetFlags::Closable : WidgetFlags::None)\r\n        };\r\n\r\n        auto controllerAndInfo = std::make_shared&lt;BasicWidgetControllerWithInfo(info);\r\n\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">auto controller = std::shared_ptr&lt;BasicWidgetController&gt;(<\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">    controllerAndInfo, &amp;controllerAndInfo-&gt;m_controller);<\/span>\r\n\r\n        m_widget = MakeWidget(controller);\r\n    }\r\n\r\n    \u27e6 other gadget stuff \u27e7\r\n};\r\n<\/pre>\n<p>We use an aliasing constructor with a pointer to the controller, but telling it to control the lifetime of the <code>Basic\u00adWidget\u00adController\u00adWith\u00adInfo<\/code>.<\/p>\n<p>Of course, all of this is a problem of the application&#8217;s own creation. They should just fix the <code>Basic\u00adWidget\u00adController<\/code> to <i>copy<\/i> the <code>Basic\u00adWidget\u00adInfo<\/code> instead of taking a reference.<\/p>\n<pre>struct BasicWidgetController : WidgetController\r\n{\r\n    BasicWidgetController(BasicWidgetInfo const&amp; info) :\r\n        m_info(info) {}\r\n\r\n    WidgetKind GetKind() override { return m_info.kind; }\r\n    WidgetFlags GetFlags() override { return m_info.flags; }\r\n\r\n    \/\/ Do not customize any dynamic actions.\r\n    void OnOpening() override { }\r\n    \u27e6 and so on \u27e7\r\n\r\nprivate:\r\n    BasicWidgetInfo <span style=\"border: solid 1px currentcolor;\">\/* <span style=\"text-decoration: line-through;\">const&amp;<\/span> *\/<\/span> m_info;\r\n}\r\n<\/pre>\n<p>Now the original code works again.<\/p>\n<pre>    void CreateWidget(GadgetFlags flags)\r\n    {\r\n        BasicWidgetInfo info = {\r\n            WidgetKind::Vanilla,\r\n            WidgetFlags::Openable |\r\n            (flags &amp; GadgetFlags::ClosableWidget ?\r\n                WidgetFlags::Closable : WidgetFlags::None)\r\n        };\r\n\r\n        auto controller = std::make_shared&lt;BasicWidgetController&gt;(info);\r\n\r\n        m_widget = MakeWidget(controller);\r\n    }\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Only on first execution.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112738","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Only on first execution.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112738","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112738"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112738\/revisions"}],"predecessor-version":[{"id":112739,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112738\/revisions\/112739"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112738"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112738"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112738"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}