{"id":6713,"date":"2012-08-31T07:00:00","date_gmt":"2012-08-31T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2012\/08\/31\/adventures-in-undefined-behavior-the-premature-downcast\/"},"modified":"2012-08-31T07:00:00","modified_gmt":"2012-08-31T07:00:00","slug":"adventures-in-undefined-behavior-the-premature-downcast","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20120831-00\/?p=6713","title":{"rendered":"Adventures in undefined behavior: The premature downcast"},"content":{"rendered":"<p>\nA customer encountered the following problem:\n<\/p>\n<pre>\nclass Shape\n{\npublic:\n    virtual bool Is2D() { return false; }\n};\nclass Shape2D : public Shape\n{\npublic:\n    virtual bool Is2D() { return true; }\n};\nShape *FindShape(Cookie cookie);\nvoid BuyPaint(Cookie cookie)\n{\n    Shape2D *shape = static_cast&lt;Shape2D *&gt;(FindShape(cookie));\n    if (shape-&gt;Is2D()) {\n       .. do all sorts of stuff ...\n    }\n}\n<\/pre>\n<p>\nThe <code>Buy&shy;Paint<\/code> function converts the cookie back\nto a <code>Shape<\/code> object, and then checks if the object\nis a <code>Shape2D<\/code> object by calling <code>Is&shy;2D<\/code>.\nIf so, then it does some more stuff to figure out what type of paint\nto buy.\n<\/p>\n<p>\n(Note to nitpickers:\nThe actual scenario was not like this, but I presented it this way\nto illustrate the point.\nIf you say &#8220;You should&#8217;ve\nused RTTI&#8221; or &#8220;You should&#8217;ve had a BuyPaint method on the Shape class&#8221;,\nthen you&#8217;re missing the point.)\n<\/p>\n<p>\nThe programmers figured they&#8217;d save some typing by casting the\nresult of <code>Find&shy;Shape<\/code> to a <code>Shape2D<\/code>\nright away,\nbecause after all, since <code>Is&shy;2D<\/code> is a virtual\nmethod, it will call the right version of the function,\neither <code>Shape::Is&shy;2D<\/code>\nor <code>Shape2D::Is&shy;2D<\/code>,\ndepending on the actual type of the underlying object.\n<\/p>\n<p>\nBut when compiler optimizations were turned on, they discovered\nthat the call to <code>Is&shy;2D<\/code> was optimized away,\nand the <code>Buy&shy;Paint<\/code> function merely assumed\nthat it was always operating on a <code>Shape2D<\/code> object.\nIt then ended up trying to buy paint even for one-dimensional objects\nlike points and lines.\n<\/p>\n<p>\nCompiler optimization bug?\nNope.\nCode bug due to reliance on undefined behavior.\n<\/p>\n<p>\nThe C++ language says (9.3.1)\n&#8220;If a nonstatic member function of a class <code>X<\/code>\nis called for an object that is not of type <code>X<\/code>,\nor of a type derived from <code>X<\/code>, the behavior is undefined.&#8221;\nIn other words,\nif you are invoking a method on an object of type <code>X<\/code>,\nthen you are promising that it really is of type <code>X<\/code>,\nor a class derived from it.\n<\/p>\n<p>\nThe code above violates this rule,\nbecause it is invoking the <code>Is&shy;2D<\/code> method\non a <code>Shape2D*<\/code>,\nwhich therefore comes with the promise\n&#8220;This really is a <code>Shape2D<\/code> object\n(or something derived from it).&#8221;\nBut this is a promise the code cannot deliver on, because the\nobject returned by <code>Find&shy;Shape<\/code> might be a simple\n<code>Shape<\/code>.\n<\/p>\n<p>\nThe compiler ran with the (false) promise and said,\n&#8220;Well, since you are guaranteeing that the object is at least\na <code>Shape2D<\/code>,\nand since I have studied your code and determined that no\nclasses which further derive from <code>Shape2D<\/code> override\nthe <code>Is&shy;2D<\/code> method,\nI have therefore proved that the <i>final overrider<\/i> is\n<code>Shape2D::Is&shy;2D<\/code> and can therefore\ninline that method.&#8221;\n<\/p>\n<p>\nResult: The compiler inlines <code>Shape2D::Is&shy;2D<\/code>,\nwhich returns <code>true<\/code>, so the &#8220;if&#8221; test can be optimized out.\nThe compiler can assume that <code>Buy&shy;Paint<\/code> is always\ncalled with cookies that represent two-dimensional objects.\n<\/p>\n<p>\nThe fix is to do the annoying typing that the original authors\nwere trying to avoid:\n<\/p>\n<pre>\nvoid BuyPaint(Cookie cookie)\n{\n    Shape *shape = FindShape(cookie);\n    if (shape-&gt;Is2D()) {\n      Shape2D *shape2d = static_cast&lt;Shape2D *&gt;(shape);\n       .. do all sorts of stuff (with shape2d) ...\n    }\n}\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>A customer encountered the following problem: class Shape { public: virtual bool Is2D() { return false; } }; class Shape2D : public Shape { public: virtual bool Is2D() { return true; } }; Shape *FindShape(Cookie cookie); void BuyPaint(Cookie cookie) { Shape2D *shape = static_cast&lt;Shape2D *&gt;(FindShape(cookie)); if (shape-&gt;Is2D()) { .. do all sorts of stuff &#8230; [&hellip;]<\/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-6713","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A customer encountered the following problem: class Shape { public: virtual bool Is2D() { return false; } }; class Shape2D : public Shape { public: virtual bool Is2D() { return true; } }; Shape *FindShape(Cookie cookie); void BuyPaint(Cookie cookie) { Shape2D *shape = static_cast&lt;Shape2D *&gt;(FindShape(cookie)); if (shape-&gt;Is2D()) { .. do all sorts of stuff &#8230; [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/6713","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=6713"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/6713\/revisions"}],"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=6713"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=6713"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=6713"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}