Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ At this point, we have a clear understanding of the complete path of a C program

### Exercise 1: Multi-File Compilation Practice

**Difficulty: Basic** · multi-file staged compile and the symbol table

Build a multi-file project containing the following files:

**utils.h**:
Expand All @@ -373,6 +375,8 @@ Please complete the following:

### Exercise 2: `printf` Formatting Practice

**Difficulty: Basic** · practice printf width, precision, alignment

Without looking up resources, write the expected output of the following `printf` statements (then compile and run to verify):

```c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ The next question arises: we've covered integers, but what about decimals? How a

### Exercise 1: Type Detector

**Difficulty: Basic** · use sizeof to map out each type's size

Write a program that prints the `sizeof` values of all the following types, and check against the standard to see if they meet the minimum guarantees:

```c
Expand All @@ -330,6 +332,8 @@ Hint: You can use a macro to reduce repetitive code.

### Exercise 2: Overflow Observation

**Difficulty: Basic** · see signed overflow UB vs unsigned wraparound

Perform overflow experiments on signed `int8_t` and unsigned `uint8_t` respectively:

```c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ At this point, we have laid a solid foundation for C language data types. Next,

### Exercise 1: Floating-Point Precision Detective

**Difficulty: Basic** · compare floats with epsilon

Predict the output of the following code, then compile and run it to verify your prediction:

```c
Expand All @@ -339,6 +341,8 @@ Modify the code to use epsilon comparison to obtain the correct result.

### Exercise 2: Implicit Conversion Pitfalls

**Difficulty: Basic** · the trap of mixing signed with size_t

The following code contains a hidden bug. Find it and explain the reason:

```c
Expand All @@ -355,16 +359,38 @@ Hint: What type does `sizeof` return?

### Exercise 3: `const` in Practice

Write a function that accepts a string and counts the occurrences of a specific character. Use `const` correctly in the function signature:
**Difficulty: Basic** · what const protects, and how to pass arguments

Read the code below and answer four questions:

```c
/// @brief 统计字符 ch 在字符串 str 中出现的次数
/// @param str 不可修改的字符串
/// @param ch 要查找的字符
/// @return 出现次数
size_t count_char(const char* str, char ch);
// sum promises not to modify what data points at
int sum(const int* data, size_t n); // (1) what does the const on the parameter promise?

void f(void) {
const int limit = 100; // (2) what does const on a local variable do?
int arr[3] = {1, 2, 3};
limit = 200; // (3) does this line compile?
sum(arr, 3); // (4) is passing int* to a const int* parameter legal?
}
```

Now flip it around: if `sum`'s parameter were `int*` (no const), and the caller passed a `const int carr[3]`, what happens and why?

::: details Reference answer

(1) const is a read-only contract: it tells callers and the compiler that `sum` won't modify the array through `data`.

(2) `limit` becomes a read-only variable that can't be reassigned, and the compiler can optimize based on it.

(3) It does not compile. `limit` is read-only; assigning to it is an error.

(4) Legal. Passing `int*` to a `const int*` parameter "tightens" the contract (writable to read-only), which is safe and implicit.

The reverse: passing `const int*` to an `int*` parameter drops the const protection, so the compiler warns or errors. You'd need an explicit cast, which is dangerous: the function could modify data it wasn't supposed to.

:::

## References

- [cppreference: Implicit conversions in C](https://en.cppreference.com/w/c/language/conversion)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,8 @@ The next question is—we haven't covered bitwise operations yet. If you plan to

### Exercise 1: Integer Division Prediction

**Difficulty: Basic** · truncation toward zero and the sign of the remainder

Without actually running it, predict the value of the following expressions, then write a program to verify:

```c
Expand All @@ -369,6 +371,8 @@ int a = 7, b = -4;

### Exercise 2: Short-Circuit Evaluation in Action

**Difficulty: Intermediate** · guard an array bound with short-circuit evaluation

Write a function that safely finds the first element in an array greater than a specified value. Use short-circuit evaluation to ensure no out-of-bounds access occurs:

```c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ At this point, we have covered all aspects of C operators. Next, we will learn a

### Exercise 1: Bit Manipulation Toolkit

**Difficulty: Intermediate** · set / clear / toggle / extract a bit field

Implement the following bit manipulation functions:

```c
Expand All @@ -307,6 +309,8 @@ bool bit_check(uint32_t val, uint8_t n);

### Exercise 2: Safe Shift

**Difficulty: Basic** · boundary checks that fend off shift UB

Write a function to safely perform a left shift, handling all boundary cases:

```c
Expand All @@ -320,6 +324,8 @@ bool safe_shift_left(uint32_t *result, uint32_t val, int n);

### Exercise 3: Expression Analysis

**Difficulty: Basic** · spot sequence points and undefined behavior

Analyze the evaluation behavior of the following expressions (without running them), marking each as "well-defined", "unspecified behavior", or "undefined behavior":

```c
Expand Down
75 changes: 72 additions & 3 deletions documents/en/vol1-fundamentals/c_tutorials/04-control-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,24 +445,93 @@ Next, we will learn about functions—how to organize code into reusable modules

### Exercise 1: Days in a Month

**Difficulty: Basic** · practice the fall-through of switch

Use `switch` to implement a function that returns the number of days in a month based on the month and whether it is a leap year. You are required to use the fall-through feature to merge months with the same number of days.

### Exercise 2: Safe Matrix Search

**Difficulty: Intermediate** · two ways to break out of nested loops

Search for a target value in a 2D matrix. Once found, break out of the multi-level loop in two ways: one using a flag variable, and one using `goto`.

```c
// TODO: Implement search_matrix_flag and search_matrix_goto
```

### Exercise 3: Waiting with Timeout
### Exercise 3: Hand-Writing a Protocol-Frame State Machine

**Difficulty: Intermediate** · a state machine with switch and an explicit state variable

The end of this chapter demonstrated a byte-driven serial protocol state machine (start byte `0xAA` -> payload length -> payload -> end byte `0x55`). Implement an equivalent parser yourself: feed received bytes one at a time to `frame_feed`, which internally uses `switch (state)` to transition between states and prints the payload once a full frame is received.

```c
#include <stdint.h>

typedef enum { STATE_IDLE, STATE_LEN, STATE_PAYLOAD, STATE_DONE } FrameState;

/// @brief Feed one byte at a time; returns 1 when a complete frame (with end byte) is received, 0 otherwise
int frame_feed(uint8_t byte);
```

Implement it with `switch` plus an explicit state variable, not a long if-else chain. One more thing to think about: if the "payload length" field sent by the peer is tampered to a value larger than your buffer, will your state machine blow up? How do you defend against it?

Implement a waiting function with a timeout mechanism to avoid deadlocks caused by naked `while` waiting:
::: details Reference answer

```c
// TODO: Implement wait_with_timeout
#include <stdio.h>
#include <stdint.h>

#define MAX_PAYLOAD 16

typedef enum { STATE_IDLE, STATE_LEN, STATE_PAYLOAD, STATE_DONE } FrameState;

static FrameState state = STATE_IDLE;
static uint8_t payload[MAX_PAYLOAD];
static uint8_t payload_len = 0;
static uint8_t payload_idx = 0;

int frame_feed(uint8_t byte) {
switch (state) {
case STATE_IDLE:
if (byte == 0xAA) { // only advance on the start byte
payload_idx = 0;
payload_len = 0;
state = STATE_LEN;
}
break;
case STATE_LEN:
// defense: the length may be tampered, clamp to the buffer cap to avoid an out-of-bounds write later
payload_len = (byte <= MAX_PAYLOAD) ? byte : MAX_PAYLOAD;
state = (payload_len == 0) ? STATE_DONE : STATE_PAYLOAD;
break;
case STATE_PAYLOAD:
payload[payload_idx++] = byte;
if (payload_idx >= payload_len) {
state = STATE_DONE;
}
break;
case STATE_DONE:
if (byte == 0x55) { // proper end byte
printf("Frame OK (%u bytes):", payload_len);
for (uint8_t i = 0; i < payload_len; i++) {
printf(" %02X", payload[i]);
}
printf("\n");
state = STATE_IDLE;
return 1;
}
state = STATE_IDLE; // end byte never came: frame broken, go idle and wait for the next 0xAA
break;
}
return 0;
}
```

The key is that every `case` explicitly states "what the next state is", which is what makes a state machine easier to read than a long if-else chain. Clamping the length in `STATE_LEN` is the most basic defense in protocol parsing: never trust a length field sent by the peer.

:::

## References

- [cppreference: switch statement](https://en.cppreference.com/w/c/language/switch)
Expand Down
56 changes: 40 additions & 16 deletions documents/en/vol1-fundamentals/c_tutorials/05-function-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,34 +341,56 @@ At this point, we have mastered the basic usage of functions. The next question

## Exercises

### Exercise 1: Variadic Log Function
### Exercise 1: Variadic Max

Implement a custom log function that supports log levels and formatted strings:
**Difficulty: Basic** · pull arguments one by one with va_arg

Following the `average` example in this chapter, implement a variadic function that returns the maximum of all its integer arguments. The first argument `count` says how many integers follow:

```c
/// @brief Return the maximum of count integers
/// @param count number of integer arguments that follow
/// @return the maximum; returns 0 if count is 0
int max_int(int count, ...);
```

Usage: `max_int(3, 10, 25, 7)` should return `25`.

**Challenge extension** (optional): if you want to implement `log_message(level, format, ...)` with real formatting, you need to forward the variadic arguments to the `printf` family, i.e. `vprintf`/`vfprintf` (not covered in this chapter). Look up `vprintf` on cppreference and then try it.

::: details Reference answer

```c
#include <stdio.h>
#include <stdarg.h>

void log_message(const char *level, const char *fmt, ...) {
// TODO: Print timestamp and log level
// TODO: Use va_list to handle formatted string
printf("[%s] ", level);
int max_int(int count, ...) {
if (count <= 0) {
return 0;
}
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf("\n");
}
va_start(args, count);

int result = va_arg(args, int);
for (int i = 1; i < count; i++) {
int next = va_arg(args, int);
if (next > result) {
result = next;
}
}

int main() {
log_message("INFO", "System started with code %d", 200);
log_message("ERROR", "Failed to open file: %s", "config.txt");
return 0;
va_end(args);
return result;
}
```

Same structure as `average`, just "accumulate then divide" becomes "compare and keep the max". The `va_start` / `va_arg` / `va_end` trio is rehearsed once more here.

:::

### Exercise 2: Recursion vs. Iteration — Binary Search

**Difficulty: Intermediate** · two ways to write the same algorithm

Implement binary search using both recursion and iteration, and compare their performance and readability:

```c
Expand Down Expand Up @@ -396,6 +418,8 @@ int main() {

### Exercise 3: Multiple Return Values in Practice

**Difficulty: Basic** · return multiple results through pointer parameters

Implement a function that calculates both the maximum and minimum values of an array:

```c
Expand Down
45 changes: 32 additions & 13 deletions documents/en/vol1-fundamentals/c_tutorials/06-scope-and-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ In actual projects, form a habit: **add `static` to all global variables and hel

### Exercise 1: Modular Counter

**Difficulty: Basic** · hide data with file-scope internal linkage

Design a simple module where the header file exposes only three functions: `counter_init`, `counter_inc`, and `counter_get`. Internally, use a `static` variable to maintain the count. External code must not be able to directly access or modify this counter variable.

```c
Expand All @@ -413,6 +415,8 @@ Please implement `counter.c` yourself.

### Exercise 2: Multi-file Symbol Visibility

**Difficulty: Intermediate** · external linkage, internal linkage, and extern together

Create three files: `data.c`, `helper.c`, and `main.c`. Requirements:

- `data.c` defines an external linkage global variable `g_sensor_data`, initial value `0`.
Expand Down Expand Up @@ -448,28 +452,43 @@ int main(void) {
}
```

### Exercise 3: Lazy Initialization
### Exercise 3: Call Counter

Use a `static` local variable to implement a `get_config` function: on the first call, perform initialization (print "Initializing..." and set default values), subsequent calls directly return the initialized value without re-initializing.
**Difficulty: Basic** · keep state across calls with a static local variable

Implement `call_count(void)`: each time it is called, it returns "which call this is". Use the property that a `static` local variable "does not get destroyed when the function returns".

```c
#include <stdio.h>
/// @return which call this is (the first call returns 1)
int call_count(void);
```

int *get_config(void) {
static int config = 0;
static int initialized = 0;
Hint: declare `static int n = 0;` inside the function, do `++n`, then return. Think some more: if you turned it into a plain local `int n = 0;` (dropping static), what would the result become, and why?

if (!initialized) {
printf("Initializing...\n");
config = 42; // Load from EEPROM or something
initialized = 1;
}
::: details Reference answer

return &config;
```c
#include <stdio.h>

int call_count(void) {
static int n = 0; // initialized once; the value survives after the function returns
++n;
return n;
}

int main(void) {
printf("%d\n", call_count()); // 1
printf("%d\n", call_count()); // 2
printf("%d\n", call_count()); // 3
return 0;
}
```

> Hint: `static` local variables are initialized only when entering the function for the first time—perfect for implementing "initialize once" semantics.
Drop `static` and `n` gets re-initialized to 0 on every entry, so `++n` returns 1 no matter how many times you call it: it loses the ability to "remember the last result".

One common confusion worth clearing up: `static int n = 0;` is initialized when the program starts (not when `call_count` is first called), and this happens only once for the whole lifetime. Because it is initialized only once and the value is preserved afterwards, it works as a counter.

:::

## Reference Resources

Expand Down
Loading
Loading