<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe" -->

---
title: I switched to C and instantly grew my hair | daily.dev
description: A live coding session working on the &#x27;knob&#x27; single-header C library. The session covers adding `todo_f` and `unreachable_f` macros with printf-style formatting...
canonical: https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: I switched to C and instantly grew my hair | daily.dev
og:description: A live coding session working on the &#x27;knob&#x27; single-header C library. The session covers adding `todo_f` and `unreachable_f` macros with printf-style formatting...
og:url: https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe
og:image: https://api.daily.dev/og/posts/VO9SXDhhe.png
og:image:alt: I switched to C and instantly grew my hair
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# I switched to C and instantly grew my hair

**[Tsoding Daily](https://daily.dev/sources/tsoding_daily)** · 101 min read · 25 upvotes · 4 comments

## Summary

A live coding session working on the 'knob' single-header C library. The session covers adding `todo_f` and `unreachable_f` macros with printf-style formatting support, introducing an `SV_LIT` macro for compile-time string view construction from literals, and porting UTF-8 character byte-length utilities (`knob_bytes_for_utf8` and `knob_sv_utf8_len`) from another project. Along the way there are discussions on semantic versioning, C macro limitations with variadic arguments, cross-compiler compatibility (GCC, Clang, MSVC), anonymous unions as a way to alias struct fields, and the pitfalls of using reserved names as macros for backward compatibility.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.youtube.com/watch?v=i3WsCXjl1Gg>

## Questions this post answers

### Why can't I name a variable or struct field stdin or stdout when compiling C code with MSVC?

MSVC defines stdin and stdout as global macros rather than scoped identifiers, so any use of those exact names anywhere in a translation unit, regardless of scope, causes the preprocessor to replace them and produces confusing compile errors that don't explain the real cause. GCC and Clang do not have this problem, since they treat stdin/stdout as ordinary variables rather than macros.

_Anyone debugging cross-compiler C portability issues can find real-world gotchas like this shared on daily.dev._

### How do I correctly measure the length of a UTF-8 character instead of counting raw bytes?

A single UTF-8 character can span multiple bytes: ASCII characters like commas and letters take one byte, Cyrillic characters take two bytes, and many Japanese characters take three bytes, so iterating byte-by-byte breaks non-ASCII text. A dedicated function that returns the byte length of the current character, then steps forward by that many bytes, is needed to correctly walk a UTF-8 string; grapheme clusters like emoji or Hindi conjuncts can still span multiple such steps.

_Developers handling internationalized text can track UTF-8 parsing techniques like this on daily.dev._

### How does semantic versioning actually decide whether a release is major, minor, or patch?

Semantic versioning is decided strictly by backward compatibility, not by how many changes were made: a major version bump happens whenever anything, even a single line, breaks existing code that depends on the library, while a minor release can contain thousands of changed lines as long as nothing breaks. Unintentional breakage is treated as a bug and fixed in a patch release rather than justifying a major bump.

_Teams debating version bumps for their own libraries can compare semver reasoning like this on daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@robertozx64** · 4 upvotes

> Damn! Mine fell when I left C for Python! If only I knew back then!

**@ezpie** · 1 upvotes

> and they'll turn blue if you use rust

## Similar posts on daily.dev

- [some C habits I employ for the modern day](https://daily.dev/posts/some-c-habits-i-employ-for-the-modern-day-xxclsjssp) · Lobsters · 2 upvotes · 0 comments
- [But what about K?](https://daily.dev/posts/but-what-about-k--efjvokeag) · Lobsters · 0 upvotes · 0 comments
- [On C extensions, portability, and alternative compilers](https://daily.dev/posts/on-c-extensions-portability-and-alternative-compilers-xh1x5m5qq) · Hacker News · 0 upvotes · 0 comments
- [a bunch of stuff i used to not know about K&R C](https://daily.dev/posts/a-bunch-of-stuff-i-used-to-not-know-about-k-r-c-tnyux5foh) · Lobsters · 0 upvotes · 0 comments

---

Tags: [#open-source](https://daily.dev/tags/open-source), [#c](https://daily.dev/tags/c)

[View this post on daily.dev](https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"I switched to C and instantly grew my hair","url":"https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe"},"datePublished":"2026-07-15T13:47:16.219Z","dateModified":"2026-09-14T07:00:50.013Z","description":"A live coding session working on the 'knob' single-header C library. The session covers adding `todo_f` and `unreachable_f` macros with printf-style formatting...","image":"https://i.ytimg.com/vi/i3WsCXjl1Gg/sddefault.jpg","thumbnailUrl":"https://i.ytimg.com/vi/i3WsCXjl1Gg/sddefault.jpg","isAccessibleForFree":true,"articleSection":"Tsoding Daily","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Tsoding Daily","logo":"https://media.daily.dev/image/upload/s--6msWm9pp--/f_auto/v1728806157/logos/tsoding_daily","url":"https://daily.dev/sources/tsoding_daily"},"commentCount":4,"discussionUrl":"https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":25},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":4}],"keywords":"open-source,c","timeRequired":"PT101M","video":{"@type":"VideoObject","name":"I switched to C and instantly grew my hair","description":"A live coding session working on the 'knob' single-header C library. The session covers adding `todo_f` and `unreachable_f` macros with printf-style formatting...","thumbnailUrl":"https://i.ytimg.com/vi/i3WsCXjl1Gg/sddefault.jpg","uploadDate":"2026-07-15T13:47:16.219Z","duration":"PT101M","url":"https://api.daily.dev/r/VO9SXDhhe","embedUrl":"https://www.youtube.com/embed/i3WsCXjl1Gg"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Tsoding Daily","item":"https://daily.dev/sources/tsoding_daily"},{"@type":"ListItem","position":3,"name":"I switched to C and instantly grew my hair"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe","comment":[{"@type":"Comment","text":"Damn! Mine fell when I left C for Python! If only I knew back then!","datePublished":"2026-07-17T00:52:00.874Z","url":"https://daily.dev/posts/VO9SXDhhe#c-klZuC7zu8","author":{"@type":"Person","name":"Roberto Dillon","url":"https://daily.dev/robertozx64","image":"https://lh3.googleusercontent.com/a/ACg8ocIVrHEFko_6VGTEU0f9pK_aAHTMZbJemzW-t7jRKVSO6VsXZj0=s96-c"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":4}},{"@type":"Comment","text":"and they’ll turn blue if you use rust","datePublished":"2026-07-18T11:17:50.162Z","url":"https://daily.dev/posts/VO9SXDhhe#c-cTXyykBBR","author":{"@type":"Person","name":"Ezpie","url":"https://daily.dev/ezpie","image":"https://avatars.githubusercontent.com/u/104765117?v=4"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/i-switched-to-c-and-instantly-grew-my-hair-vo9sxdhhe#faq","mainEntity":[{"@type":"Question","name":"Why can't I name a variable or struct field stdin or stdout when compiling C code with MSVC?","acceptedAnswer":{"@type":"Answer","text":"MSVC defines stdin and stdout as global macros rather than scoped identifiers, so any use of those exact names anywhere in a translation unit, regardless of scope, causes the preprocessor to replace them and produces confusing compile errors that don't explain the real cause. GCC and Clang do not have this problem, since they treat stdin/stdout as ordinary variables rather than macros. Anyone debugging cross-compiler C portability issues can find real-world gotchas like this shared on daily.dev."}},{"@type":"Question","name":"How do I correctly measure the length of a UTF-8 character instead of counting raw bytes?","acceptedAnswer":{"@type":"Answer","text":"A single UTF-8 character can span multiple bytes: ASCII characters like commas and letters take one byte, Cyrillic characters take two bytes, and many Japanese characters take three bytes, so iterating byte-by-byte breaks non-ASCII text. A dedicated function that returns the byte length of the current character, then steps forward by that many bytes, is needed to correctly walk a UTF-8 string; grapheme clusters like emoji or Hindi conjuncts can still span multiple such steps. Developers handling internationalized text can track UTF-8 parsing techniques like this on daily.dev."}},{"@type":"Question","name":"How does semantic versioning actually decide whether a release is major, minor, or patch?","acceptedAnswer":{"@type":"Answer","text":"Semantic versioning is decided strictly by backward compatibility, not by how many changes were made: a major version bump happens whenever anything, even a single line, breaks existing code that depends on the library, while a minor release can contain thousands of changed lines as long as nothing breaks. Unintentional breakage is treated as a bug and fixed in a patch release rather than justifying a major bump. Teams debating version bumps for their own libraries can compare semver reasoning like this on daily.dev."}}]}
```

