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
244: Attributes in C23: [[nodiscard]] and [[deprecated]]
Imagine you're working in a busy professional kitchen. The head chef hands you a tray of incredibly expensive saffron and says, "Take this to the pastry station." If you just set the tray down on a random counter and walk away without making sure the pastry chef actually received it, you've just wasted a huge amount of money. In that scenario, the head chef's instruction wasn't just "deliver this," it was "ensure this delivery is acknowledged."
Now, imagine there's an old, temperamental meat slicer in the corner. There's a sticky note on it that says, "This slicer is being replaced on Friday; please start using the new industrial one in the cold room." The old slicer still cuts meat, but using it is a sign that you're out of the loop and ignoring the new workflow.
In C23, attributes like [[nodiscard]] and [[deprecated]] are exactly like those kitchen instructions. They aren't laws that stop the code from compiling—the code will still "run"—but they are loud, clear signals to the developer (and the compiler) that something is being handled poorly.
Stop Ignoring Your Return Values
I can't tell you how many bugs I've chased down that were caused by someone ignoring a return value. Usually, it's a function that returns an error code or a pointer to newly allocated memory. If you call a function that returns a NULL on failure and you don't check it, you're just praying that the program doesn't segfault five minutes later.
[[nodiscard]] tells the compiler: "If the caller ignores the return value of this function, throw a warning." It turns a silent failure into a loud build-time alert.
#include <stdio.h>
#include <stdlib.h>
[[nodiscard]] void* secure_malloc(size_t size) {
printf("Allocating %zu bytes securely...\n", size);
return malloc(size);
}
int main() {
// This will trigger a compiler warning in C23
// because we are ignoring the returned pointer!
secure_malloc(1024);
return 0;
}
In the example above, ignoring secure_malloc is a disaster waiting to happen. By marking it [[nodiscard]], I'm forcing you to at least acknowledge the pointer, which usually leads you to actually check if it's NULL.
Handling the Old Guard
Software evolves. You'll eventually realize that a function you wrote six months ago is inefficient, buggy, or just poorly named. But you can't just delete it—you might have a hundred other files relying on it. This is where [[deprecated]] comes in.
Instead of deleting the function and breaking the whole build, you mark it as deprecated. This tells anyone using the function, "This still works for now, but you really should move to the new version before we eventually delete this." You can even provide a helpful message to guide them.
#include <stdio.h>
// The old way of calculating a checksum
[[deprecated("Use calculate_checksum_v2() for better collision resistance")]]
unsigned int old_checksum(const char* data) {
return 0; // Simplified for the example
}
// The new, improved way
unsigned int calculate_checksum_v2(const char* data) {
return 12345;
}
int main() {
// This will compile, but the compiler will nag you with a warning
unsigned int sum = old_checksum("Hello C23");
return 0;
}
I love this feature because it turns the compiler into a documentation tool. Instead of hunting through a PDF or a Wiki to find out that a function is obsolete, the warning pops up right in your IDE the moment you try to use it.
When to Actually Use These
Don't go overboard. If you put [[nodiscard]] on every single function, you'll create "warning fatigue," and people will start ignoring the compiler entirely. Use it for things that must be checked: memory allocations, status codes, or functions where the return value is the entire point of the function (like sqrt()—why call square root if you don't use the result?).
Use [[deprecated]] whenever you're transitioning an API. It's the polite way to tell your teammates (or your future self) that there's a better way to do things without causing a massive breakage in the codebase.
📋 Practical Task
Refactoring the Legacy System Telemetry
You have inherited a piece of telemetry code for a satellite system. It's old, and some of the functions are dangerous or obsolete. Your task is to apply C23 attributes to make the code safer and guide other developers toward the new API.
Requirements:
- Find the
get_satellite_statusfunction. It returns a critical status code that must be checked to prevent system crashes. Mark it so the compiler warns anyone who ignores the return value. - Find the
send_data_legacyfunction. It is being replaced bysend_data_secure. Mark the legacy function as deprecated and include a message telling the user to switch tosend_data_secure.
#include <stdio.h>
// This returns 0 for OK, -1 for ERROR.
// It is dangerous to ignore this!
int get_satellite_status() {
return 0;
}
// This is the old, insecure way of sending data.
void send_data_legacy(const char* msg) {
printf("Sending insecurely: %s\n", msg);
}
// This is the new, encrypted way of sending data.
void send_data_secure(const char* msg) {
printf("Sending securely: %s\n", msg);
}
int main() {
// TASK: These two lines should trigger compiler warnings after your changes
get_satellite_status();
send_data_legacy("Ping");
return 0;
}
There are no comments for now.