<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[mstaali pub]]></title><description><![CDATA[mstaali pub]]></description><link>https://mstaali-blog.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6a9479ca8b2e47a9f8030418/05766936-a34a-45ce-a43d-1c744cc41c11.png</url><title>mstaali pub</title><link>https://mstaali-blog.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 05 Sep 2026 05:29:05 GMT</lastBuildDate><atom:link href="https://mstaali-blog.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Three Lines That Only Compiled on a Mac]]></title><description><![CDATA[Every C and C++ project I wrote at 1337 was built on a Mac, with clang, under -Wall -Wextra -Werror. All of them compiled clean since the grading script will not bother running a binary that produced ]]></description><link>https://mstaali-blog.hashnode.dev/three-lines-that-only-compiled-on-a-mac</link><guid isPermaLink="true">https://mstaali-blog.hashnode.dev/three-lines-that-only-compiled-on-a-mac</guid><category><![CDATA[C]]></category><category><![CDATA[C++]]></category><category><![CDATA[compiler]]></category><category><![CDATA[Portability]]></category><dc:creator><![CDATA[mstaali]]></dc:creator><pubDate>Sun, 30 Aug 2026 20:00:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9479ca8b2e47a9f8030418/7baf51fa-c954-4bde-ab21-8b512f3ad98b.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Every C and C++ project I wrote at 1337 was built on a Mac, with clang, under <code>-Wall -Wextra -Werror</code>. All of them compiled clean since the grading script will not bother running a binary that produced a warning.</p>
<p>So when I rebuilt my repos on Linux under GCC, I expected all green builds. Boy I was wrong.</p>
<p>The three failures ware not caused by a bug. The programs were correct. They did what they were supposed to do, and they still do. What broke was that I had been writing against one compiler's opinion and calling it the language.</p>
<p>Here are the three lines, the exact diagnostics from both toolchains, and what each one actually taught me. Everything below was reproduced on Ubuntu 24.04 with GCC 13.3.0 and clang 18.1.3.</p>
<hr />
<h2>1. Subscripting a <code>void *</code></h2>
<p>This is <code>ft_memchr</code> from my libft — the first project, the one where you reimplement the C standard library.</p>
<pre><code class="language-c">void	*ft_memchr(const void *str, int c, size_t size)
{
	unsigned char	occ;
	unsigned char	*s;
	size_t			i;

	occ = (unsigned char)c;
	s = (unsigned char *)str;
	i = 0;
	while (i &lt; size)
	{
		if (occ == s[i])
			return ((void *)&amp;str[i]);
		i++;
	}
	return (NULL);
}
</code></pre>
<p>Look at the return. I compare using <code>s[i]</code> — the <code>unsigned char *</code> I cast at the top of the function, exactly as intended — and then return the address of <code>str[i]</code>, where <code>str</code> is still a <code>const void *</code>.</p>
<p><code>str[i]</code> means <code>*(str + i)</code>. You cannot do arithmetic on a <code>void *</code>, because <code>void</code> has no size, so the compiler has nothing to multiply <code>i</code> by.</p>
<p>GCC says so :</p>
<pre><code class="language-plaintext">ft_memchr.c:27:45: error: dereferencing 'void *' pointer [-Werror]
   27 |                         return ((void *)&amp;str[i]);
      |                                             ^
cc1: all warnings being treated as errors
</code></pre>
<p>clang, same file, same <code>-Wall -Wextra -Werror</code>, says nothing at all. Exit code zero.</p>
<p>Both compilers implement the same GNU extension: treat <code>sizeof(void)</code> as 1 and let the arithmetic through. The difference is entirely in how loudly each one admits to it. clang only mentions it if you ask for strict standard conformance :</p>
<pre><code class="language-plaintext">$ clang -Wall -Wextra -Werror -pedantic -c ft_memchr.c
ft_memchr.c:27:24: error: subscript of a pointer to void is a GNU extension [-Werror,-Wgnu-pointer-arith]
</code></pre>
<p>That is the whole story of case one: <strong>clang has a named warning for this and keeps it off by default; GCC has an unnamed warning for this and keeps it on.</strong></p>
<p>The "unnamed" part surprised me. GCC's message ends in <code>[-Werror]</code>, not <code>[-Werror=something]</code>, and that is not a formatting quirk. The warning has no flag name, so there is no <code>-Wno-</code> that turns it off. I tried <code>-Wno-pointer-arith</code> and got the warning anyway; that flag controls the <code>-pedantic</code> phrasing, not this one. GCC has decided this is not a matter of taste.</p>
<p>The fix is a single character, and the right variable was already sitting two lines up :</p>
<pre><code class="language-c">return ((void *)&amp;s[i]);
</code></pre>
<p>Which, if I am honest, is what I meant the first time.</p>
<hr />
<h2>2. <code>std::strtod</code> without <code>&lt;cstdlib&gt;</code></h2>
<p>From <code>mode.cpp</code> in my IRC server — the <code>+l</code> handler, parsing a channel user limit :</p>
<pre><code class="language-cpp">double limit = std::strtod(partsCmd[pos].c_str(), NULL);
</code></pre>
<p>The file includes exactly one header of its own :</p>
<pre><code class="language-cpp">#include "../inc/parse.hpp"
</code></pre>
<p>which reaches <code>&lt;string&gt;</code>, <code>&lt;vector&gt;</code>, <code>&lt;iostream&gt;</code>, <code>&lt;sstream&gt;</code>, and my own classes. Nothing in that tree includes <code>&lt;cstdlib&gt;</code> — I grepped the whole <code>inc/</code> directory to be sure — and <code>&lt;cstdlib&gt;</code> is where <code>std::strtod</code> is declared.</p>
<p>GCC :</p>
<pre><code class="language-plaintext">src/mode.cpp:208:61: error: 'strtod' is not a member of 'std'; did you mean 'strtok'?
  208 |    double limit = std::strtod(partsCmd[pos].c_str(), NULL);
</code></pre>
<p>(No, GCC, I did not mean <code>strtok</code>.)</p>
<p>I filed this one under "GCC is stricter" and moved on. Then I ran it through clang on Linux to confirm, and clang failed too :</p>
<pre><code class="language-plaintext">src/mode.cpp:208:26: error: no member named 'strtod' in namespace 'std'
</code></pre>
<p>Which means my theory was wrong. This was never about the compiler.</p>
<p><code>std::strtod</code> is not part of <code>&lt;string&gt;</code> or <code>&lt;iostream&gt;</code> on any implementation. It is only ever visible because some <em>other</em> header quietly included <code>&lt;cstdlib&gt;</code> on the way past — and whether that happens is decided by the standard library, not the compiler. macOS clang defaults to <strong>libc++</strong>. Linux defaults to <strong>libstdc++</strong>.</p>
<p>Same clang, same file, same flags, only the standard library swapped :</p>
<pre><code class="language-plaintext">$ clang++ -std=c++98 -fsyntax-only src/mode.cpp
src/mode.cpp:208:26: error: no member named 'strtod' in namespace 'std'

$ clang++ -std=c++98 -fsyntax-only -nostdinc++ -isystem …/c++/v1 src/mode.cpp
$ echo $?
0
</code></pre>
<p>And libc++ is not being sloppy by accident. It is right there in the source of <code>&lt;string&gt;</code>:</p>
<pre><code class="language-cpp">#if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) &amp;&amp; _LIBCPP_STD_VER &lt;= 20
#  include &lt;algorithm&gt;
#  include &lt;concepts&gt;
#  include &lt;cstdlib&gt;
…
#endif
</code></pre>
<p>A deliberate compatibility block, with a macro to switch it off, and an upper bound on the language version. Which one of my headers leaks the symbol depends on both :</p>
<table>
<thead>
<tr>
<th>header included</th>
<th>libc++ (C++98)</th>
<th>libc++ (C++23)</th>
<th>libstdc++</th>
</tr>
</thead>
<tbody><tr>
<td><code>&lt;string&gt;</code></td>
<td>ok</td>
<td><strong>error</strong></td>
<td>error</td>
</tr>
<tr>
<td><code>&lt;iostream&gt;</code></td>
<td>ok</td>
<td>ok</td>
<td>error</td>
</tr>
<tr>
<td><code>&lt;vector&gt;</code></td>
<td>ok</td>
<td>ok</td>
<td>error</td>
</tr>
<tr>
<td><code>&lt;sstream&gt;</code></td>
<td>ok</td>
<td>ok</td>
<td>error</td>
</tr>
</tbody></table>
<p>The <code>&lt;string&gt;</code> route has already been withdrawn above C++20. The others still work, for now, on one implementation, as an explicitly transitional favor.</p>
<p>This happens because I was relying on a header I never named. The fix is the line I should have written in 2024 :</p>
<pre><code class="language-cpp">#include &lt;cstdlib&gt;
</code></pre>
<p><strong>Include what you use.</strong></p>
<hr />
<h2>3. A clamp written as <code>&amp;&amp;</code></h2>
<p>From cub3D the raycaster, a two-person project; this particular file is my teammate's. It broke my build all the same, and it is the most interesting of the three.</p>
<pre><code class="language-c">(mlx-&gt;wall_start &lt; 0.0) &amp;&amp; (mlx-&gt;wall_start = 0.0);
(mlx-&gt;wall_end &gt; mlx-&gt;height) &amp;&amp; (mlx-&gt;wall_end = mlx-&gt;height);
</code></pre>
<p>That is a clamp. <code>&amp;&amp;</code> short-circuits, so the assignment on the right only runs when the test on the left is true. It is legal C, it is correct, and it does exactly what an <code>if</code> would do.</p>
<p>GCC :</p>
<pre><code class="language-plaintext">ray_casting.c:68:41: error: value computed is not used [-Werror=unused-value]
   68 |   (mlx-&gt;wall_start &lt; 0.0) &amp;&amp; (mlx-&gt;wall_start = 0.0);
      |                           ^~
ray_casting.c:69:47: error: value computed is not used [-Werror=unused-value]
</code></pre>
<p>The expression produces a <code>bool</code>. Nobody reads it. <code>-Wunused-value</code> lives inside <code>-Wall</code>, so <code>-Wall -Werror</code> makes it fatal.</p>
<p>clang is silent. Not just at <code>-Wall -Wextra -Werror</code> — at <code>-Weverything</code>, which turns on literally every warning clang ships. I ran it on the file and counted what came back :</p>
<pre><code class="language-plaintext">1 × [-Wcomma]
5 × [-Wfloat-conversion]
1 × [-Wpadded]
0 × [-Wunused-value]
</code></pre>
<p>Zero. clang does not consider this an unused value at any setting. It does have an opinion about those two lines, but a completely different one :</p>
<pre><code class="language-plaintext">ray_casting.c:68:47: warning: implicit conversion turns floating-point number into integer: 'double' to '_Bool' [-Wfloat-conversion]
   68 |   (mlx-&gt;wall_start &lt; 0.0) &amp;&amp; (mlx-&gt;wall_start = 0.0);
      |                           ~~  ~~~~~~~~~~~~~~~~^~~~~
</code></pre>
<p>That is not "you threw away a value." That is "your <code>double</code> became a <code>bool</code>" , not in <code>-Wall</code> by the way.</p>
<p>And the one <code>-Wcomma</code> is on a line I have not shown you yet, three lines down in the same function:</p>
<pre><code class="language-c">1 &amp;&amp; (a += step, screen_x++);
</code></pre>
<p>A constant <code>1</code>, a <code>&amp;&amp;</code> that can never short-circuit, and a comma operator doing two updates in one statement. GCC which just failed the build twice over the lines above has nothing to say about this one. clang, which said nothing about those, calls it out:</p>
<pre><code class="language-plaintext">ray_casting.c:71:18: warning: possible misuse of comma operator here [-Wcomma]
</code></pre>
<p>Two mature compilers. Neither found what the other found.</p>
<p>So why write any of this?</p>
<p>cub3D is graded against the Norm, 1337's style checker, and the Norm caps a function body at 25 lines. <code>ray_casting()</code> is 23. Hard line budget. It was written in that exact way to buy three lines back. Which happens to be what GCC rejects.</p>
<p>The fix, once the function is split so there is room for it :</p>
<pre><code class="language-c">if (mlx-&gt;wall_start &lt; 0.0)
	mlx-&gt;wall_start = 0.0;
if (mlx-&gt;wall_end &gt; mlx-&gt;height)
	mlx-&gt;wall_end = mlx-&gt;height;
</code></pre>
<hr />
<h2>What I actually took from this</h2>
<p>All three fixes are trivial. One character, one <code>#include</code>, one <code>if</code>. All three now compile clean under GCC <strong>and</strong> clang at <code>-Wall -Wextra -Werror -pedantic</code>, which is a stronger claim than either of them made alone.</p>
<p>I used to read <code>-Wall -Wextra -Werror</code> as <em>the code is fine</em>. It is not. It is <em>one implementation had no objection</em>, and implementations object to different things :</p>
<ul>
<li><p>GCC failed the build on cases 1 and 3. clang passed both without a word, and kept passing at <code>-Weverything</code> — the most aggressive setting it has still misses what plain <code>-Wall</code> catches on GCC.</p>
</li>
<li><p>It runs the other way too. clang flagged the comma operator on line 71 that GCC ignored completely. Neither compiler is the strict one; they are strict about different things.</p>
</li>
</ul>
<p>Three things I do now :</p>
<ol>
<li><p><code>-pedantic</code><strong>, always.</strong> It is what turned case 1 from an invisible extension into a named diagnostic, <code>-Wgnu-pointer-arith</code>, on the compiler that was otherwise happy.</p>
</li>
<li><p><strong>Build on both, in CI.</strong> Two jobs, <code>gcc</code> and <code>clang</code>, same flags. It is a few lines of YAML and it would have caught every one of these.</p>
</li>
<li><p><strong>Name the standard library, not just the compiler.</strong> "It builds with clang" is half a sentence. On macOS it means libc++. On Linux it usually does not.</p>
</li>
</ol>
<p>None of this is advanced. It is the sort of thing you learn the first time someone else tries to build your code which, for most of us, is the first time it matters.</p>
<p>The repos are on <a href="https://github.com/Simow03">GitHub</a> if you want to see the before and after.</p>
]]></content:encoded></item></channel></rss>