Skip to content

Commit b652c2a

Browse files
committed
Modernize C cards against current best practices
Update outdated keyword descriptions (auto for C23 type inference, register as vestigial), fix unsafe idioms (strcpy → snprintf, while(!feof) → while(fgets)), correct main() void-vs-empty-parens guidance, and add new cards for <stdint.h>, size_t, and <inttypes.h> format macros.
1 parent 4b0df65 commit b652c2a

1 file changed

Lines changed: 164 additions & 21 deletions

File tree

c/cards.md

Lines changed: 164 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ name: C Flashcards
44

55
---
66

7-
Give a local variable a local lifetime.
7+
Infer a variable's type from its initializer (C23).
8+
Previously a no-op storage-class specifier for automatic lifetime.
89

910
. . .
1011

1112
`auto`
1213

14+
```c
15+
auto x = 5; // C23: x is int
16+
auto y = 3.14; // C23: y is double
17+
```
18+
1319
---
1420

1521
Exit out of a compound statement.
@@ -148,7 +154,9 @@ A long integer data type.
148154

149155
---
150156

151-
Declare a variable to be stored in a CPU register.
157+
Hint that a variable should be stored in a CPU register.
158+
Vestigial: modern compilers ignore this and decide register allocation
159+
themselves. Its only remaining effect is forbidding `&` on the variable.
152160

153161
. . .
154162

@@ -268,19 +276,24 @@ How does it look?
268276
The `main` function.
269277

270278
```c
271-
int main() {
279+
int main(void) {
272280
// some code
273281
}
274282
```
275283
276-
Or, more correctly (recent standard):
284+
Or, when accepting command-line arguments:
277285
278286
```c
279287
int main(int argc, char *argv[]) {
280288
// some code
281289
}
282290
```
283291

292+
Note: write `(void)` rather than `()` for the no-argument form.
293+
In C17 and earlier, `()` means "unspecified parameters", not "no
294+
parameters". C23 finally makes them equivalent, but `(void)` is portable
295+
across all standards.
296+
284297
---
285298

286299
How is a variable declared?
@@ -296,6 +309,112 @@ double number;
296309
char letter;
297310
```
298311

312+
For integers where the exact width matters, prefer the fixed-width
313+
types from `<stdint.h>` (covered in a later card).
314+
315+
---
316+
317+
When should I use `<stdint.h>` types like `int32_t` instead of `int` / `long`?
318+
319+
. . .
320+
321+
The plain types (`int`, `long`, `long long`) have implementation-defined
322+
widths. On most 64-bit Unix systems `long` is 64 bits; on 64-bit Windows
323+
it's 32 bits. Use the fixed-width types when that difference matters:
324+
325+
- Binary protocols, file formats, wire formats
326+
- Hardware registers, memory-mapped I/O
327+
- Hashing, checksums, anywhere overflow semantics depend on exact width
328+
- Cross-platform code that must behave identically everywhere
329+
330+
```c
331+
#include <stdint.h>
332+
333+
int32_t signed_32bit;
334+
uint64_t unsigned_64bit;
335+
336+
// "at least N bits" — smallest type with ≥ N bits
337+
int_least16_t at_least_16;
338+
339+
// "fastest type with ≥ N bits" — often wider than asked, picked for speed
340+
int_fast32_t fast_counter;
341+
```
342+
343+
Don't reach for them for ordinary loop counters or arithmetic — plain
344+
`int` is idiomatic and matches stdlib APIs. Overusing `uint32_t` is
345+
noise; using it for an array index on a 64-bit system is worse than
346+
`size_t` because it forces a truncation.
347+
348+
---
349+
350+
What type should I use for sizes, array indices, and the result of `sizeof`?
351+
352+
. . .
353+
354+
`size_t` (from `<stddef.h>`, also pulled in by `<stdio.h>`, `<stdlib.h>`,
355+
`<string.h>`). It's an unsigned type wide enough to represent the size
356+
of any object the platform supports — 32 bits on 32-bit systems, 64 bits
357+
on 64-bit systems.
358+
359+
```c
360+
#include <stddef.h>
361+
#include <string.h>
362+
363+
size_t len = strlen("hello"); // strlen returns size_t
364+
size_t n = sizeof(int); // sizeof yields size_t
365+
366+
int arr[100];
367+
for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
368+
arr[i] = 0;
369+
}
370+
```
371+
372+
Related:
373+
374+
- `ptrdiff_t` — signed type for the difference between two pointers
375+
- `ssize_t` — POSIX signed counterpart to `size_t` (e.g. `read`, `write`
376+
return values)
377+
378+
Don't substitute `int`, `unsigned`, or `uint64_t` here — APIs like
379+
`strlen`, `memcpy`, `fread` already use `size_t`, and mismatches cause
380+
warnings or silent truncation.
381+
382+
---
383+
384+
How do I `printf` / `scanf` a fixed-width integer type portably?
385+
386+
. . .
387+
388+
You can't hardcode `%d` or `%lld` — the right specifier depends on
389+
what `int64_t` actually maps to on the target platform.
390+
391+
`<inttypes.h>` provides format-string macros: `PRI` for printf, `SCN`
392+
for scanf, followed by the conversion letter (`d`, `i`, `u`, `x`, `o`)
393+
and the width.
394+
395+
```c
396+
#include <inttypes.h>
397+
#include <stdint.h>
398+
#include <stdio.h>
399+
400+
int64_t big = 1234567890123;
401+
uint32_t small = 42;
402+
403+
printf("big = %" PRId64 ", small = %" PRIu32 "\n", big, small);
404+
405+
uint64_t parsed;
406+
scanf("%" SCNu64, &parsed);
407+
```
408+
409+
The macros are string literals, so adjacent-string-literal concatenation
410+
glues them into the format string at compile time. Common ones:
411+
412+
- `PRId32`, `PRIu32`, `PRIx64`, `PRIo16`
413+
- `SCNd32`, `SCNu64`
414+
415+
Including `<inttypes.h>` also includes `<stdint.h>`, so you don't need
416+
both.
417+
299418
---
300419
301420
How can the value of a variable be changed/stored?
@@ -319,9 +438,13 @@ Which function prints to the output screen (stdout)?
319438
`printf`
320439

321440
```c
322-
printf("Hello World");
441+
printf("Hello World\n");
323442
```
324443
444+
Include the trailing `\n` — without it, output may not be flushed
445+
before the program exits (stdout is line-buffered when attached to a
446+
terminal).
447+
325448
---
326449
327450
Why declare a function instead of just defining/implementing it?
@@ -360,15 +483,26 @@ How do I assign a new value to a char array/buffer?
360483

361484
. . .
362485

363-
Use `strcpy(char *dest, char *src)`.
364-
Make sure `dest` has enough space to contain the source string.
486+
Prefer a bounded copy. `strcpy` doesn't check that `dest` has enough
487+
space and is a frequent source of buffer overflows — static analyzers
488+
and security guidelines (CERT, MISRA) flag unchecked uses.
489+
490+
Safer options:
365491

366492
```c
367-
char bufArray[251];
368-
char *oriText = "the quick fox";
369-
strcpy(bufArray, oriText);
493+
char bufArray[256];
494+
const char *oriText = "the quick fox";
495+
496+
// snprintf is portable and always null-terminates:
497+
snprintf(bufArray, sizeof(bufArray), "%s", oriText);
498+
499+
// strlcpy (BSD, glibc 2.38+) also truncates safely:
500+
strlcpy(bufArray, oriText, sizeof(bufArray));
370501
```
371502
503+
Avoid `strncpy` for this — it doesn't guarantee null-termination
504+
and pads with zeros up to the size limit.
505+
372506
---
373507
374508
What does the following code do?
@@ -418,6 +552,9 @@ Common operators:
418552
Examples:
419553

420554
```c
555+
#include <stdbool.h> // needed for true/false/bool pre-C23
556+
// (C23 makes them keywords)
557+
421558
if (true || false) { … }
422559
if (a == b) { … }
423560
if (a != b) { … }
@@ -444,7 +581,7 @@ struct Person {
444581
};
445582
446583
struct Person myself; // uses Person as datatype
447-
strcpy(myself.Name, "John");
584+
snprintf(myself.Name, sizeof(myself.Name), "%s", "John");
448585
myself.Age = 31;
449586
```
450587

@@ -470,7 +607,7 @@ typedef struct Person FBProfile;
470607
MyInteger a = 5;
471608

472609
FBProfile guy;
473-
strcpy(guy.Name, "Jack");
610+
snprintf(guy.Name, sizeof(guy.Name), "%s", "Jack");
474611
guy.Age = 15;
475612
```
476613
@@ -482,19 +619,25 @@ How do I read a file?
482619
483620
Include `stdio.h` and call `fopen` with the filename and read (`"r"`) mode
484621
as parameters.
485-
`fopen` returns a pointer to a `FILE` datatype.
486-
Use this pointer to read or write.
622+
`fopen` returns a pointer to a `FILE` datatype, or `NULL` on failure.
487623
488-
Use `fgets` to read a line.
489-
Use `feof` to check if the end of the file has been reached.
624+
Loop on the return value of `fgets` — don't loop on `!feof(...)`.
625+
`feof` only becomes true *after* a failed read, so `while (!feof(fp))`
626+
runs the body one extra time with stale data. Checking `fgets` directly
627+
handles both EOF and read errors.
490628
491629
```c
492-
FILE *filepointer = fopen("C:\\input.txt", "r");
493-
char tmpBuffer[251];
630+
FILE *filepointer = fopen("/tmp/input.txt", "r");
631+
if (filepointer == NULL) {
632+
perror("fopen");
633+
return 1;
634+
}
635+
636+
char tmpBuffer[256];
494637
495-
while (!feof(filepointer)) { // loop while not end-of-file
496-
// read 250 chars into tmpBuffer, or until a newline or end-of-file
497-
fgets(tmpBuffer, 250, filepointer);
638+
// fgets returns NULL on EOF or error
639+
while (fgets(tmpBuffer, sizeof(tmpBuffer), filepointer) != NULL) {
640+
// process tmpBuffer
498641
}
499642
500643
fclose(filepointer); // close the file nicely for other applications

0 commit comments

Comments
 (0)