C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
243: Typeof Operators (typeof and typeof_unqual)
If you've been following along, you know that C is historically very strict about types. Usually, if you want a variable to be the same type as another, you have to explicitly name that type. But with C23, we finally got typeof and typeof_unqual. These are game-changers for writing macros and handling complex types without repeating yourself.
Why would I use typeof instead of just naming the type?
On the surface, it seems redundant. Why write typeof(x) when you can just write int? The real power kicks in when you're dealing with complex pointers, deeply nested structs, or types that are defined by the platform. I've spent way too much time in the past updating a type name in five different places because a uint32_t became a uint64_t.
Take this example. Imagine you're working with a complex buffer structure where the data type might change based on the hardware header:
struct Packet {
uint32_t id;
float payload[16];
};
struct Packet pkt;
// Instead of remembering it's a float, I just mirror the type of the element
typeof(pkt.payload[0]) temporary_value = pkt.payload[0];
If I later change payload to double, temporary_value updates automatically. It keeps your code "DRY" (Don't Repeat Yourself) and prevents those annoying precision-loss warnings that pop up when you forget to update a temporary variable's type.
What exactly does typeof_unqual do that typeof doesn't?
This is where things get interesting. typeof is a literal mirror; it copies everything, including qualifiers like const, volatile, and restrict. Most of the time, that's what you want. But sometimes, you want the base type without the restrictions.
I'll show you what I mean with a const variable. If you use typeof on a constant, the new variable is also constant, which means you can't modify it. typeof_unqual strips those qualifiers away.
const int max_limit = 100;
typeof(max_limit) a = max_limit;
// 'a' is now 'const int'. Trying to do 'a = 10' will trigger a compiler error.
typeof_unqual(max_limit) b = max_limit;
// 'b' is just a plain 'int'. I can modify it however I want.
b = 110; // Perfectly legal.
I find typeof_unqual incredibly useful when I'm writing a function that needs to take a value from a read-only configuration struct and modify it locally before using it in a calculation.
Can I use these to make my macros more flexible?
Absolutely. This is probably the primary reason these were added to the language. Before C23, if you wanted to write a "generic" macro that stored a value in a temporary variable (to avoid the double-evaluation bug), you were stuck. You either had to force the user to pass the type as an argument or rely on void* and casting, which is ugly and dangerous.
Now, you can write a macro that adapts to whatever the user throws at it:
#define SAFE_SQUARE(x) ({ \
typeof(x) _temp = (x); \
_temp * _temp; \
})
int i = 5;
float f = 2.5f;
int res1 = SAFE_SQUARE(i); // _temp is int
float res2 = SAFE_SQUARE(f); // _temp is float
Notice how _temp automatically assumes the correct type? This makes your macros behave much more like C++ templates or generic functions in other languages, while staying within the realm of standard C.
📋 Practical Task
Build a Type-Agnostic Local Maximum Tracker
You are building a system that tracks the "highest value seen so far" for various sensors. Some sensors report as int, others as double, and some as unsigned short.
Create a macro called UPDATE_MAX that takes two arguments: a variable representing the current maximum (current_max) and a new value (new_val). The macro should:
- Use
typeofto create a temporary variable that holds the value ofnew_valto ensure it is only evaluated once. - Compare the temporary variable to
current_max. - Update
current_maxif the temporary value is larger.
Test your macro with at least two different types (e.g., an int pair and a float pair) to verify that the compiler doesn't throw type-mismatch warnings and that the values update correctly.
There are no comments for now.