Skip to content

Commit

Permalink
docs: update daily 12.7
Browse files Browse the repository at this point in the history
  • Loading branch information
45gfg9 committed Dec 7, 2023
1 parent f4d568d commit ed252ce
Showing 1 changed file with 44 additions and 53 deletions.
97 changes: 44 additions & 53 deletions docs/programming/daily/2023.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,43 @@ h5:before {content: unset;}

### 「7」 The differance between `strlen` and `sizeof`.

Read the following code, then give the possible result.

```c linenums="1"
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[]="abcd";
char arr2[]={'a','b','c','d'};
printf("strlen1=%d\n",strlen(arr1));
printf("strlen2=%d\n",strlen(arr2));
printf("sizeof1=%d\n",sizeof(arr1));
printf("sizeof2=%d\n",sizeof(arr2));
char arr1[] = "abcd";
char arr2[] = {'a', 'b', 'c', 'd'};
printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr2));
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
return 0;
}
```

Result:

```text
strlen1=____
strlen2=____
sizeof1=____
sizeof2=____
```
What are the results?

<div style="display: flex">
<div style="width: 100%">A. 4 4 5 4</div>
<div style="width: 100%">B. 4 4 4 4</div>
<div style="width: 100%">C. 5 4 5 4</div>
<div style="width: 100%">D. 5 5 4 4</div>
<div style="width: 100%">A. 4, 4, 4, 4</div>
<div style="width: 100%">B. 5, 4, 4, 4</div>
<div style="width: 100%">C. 4, 4, 4, indeterminable</div>
<div style="width: 100%">D. 5, 4, 4, indeterminable</div>
</div>

<!-- prettier-ignore-start -->
??? note "Answer"

A.
D.

`arr1` has `\0` at the end, but `arr2` doesn't.

The function `strlen` means the number of steps from the given address to the first byte which includes `\0`. But the other one means total bytes in front of `\0`(`\0` is not included). We just gave `\0` in `arr1`, but didn't do so in `arr2`.
The `sizeof` operator returns the size of its operand in bytes. In this case, the size of `arr1` is 5, because it contains 4 characters and a null character `\0`. The size of `arr2` is 4, because it contains just 4 characters. The `\0` is absent in `arr2`.

So indeed, the second num is totally random, because we can't know where we can find `\0`. `4` is just one possibility.
The `strlen` function returns the length of the string, that is, the number of characters in the string before the null character `\0`. In this case, the length of `arr1` is 4, because it contains 4 characters before the null character `\0`. But the length of `arr2` is indeterminable, because it does not contain a null character `\0`. The `strlen` function will continue to read memory until it finds a null character `\0`, resulting totally random result. If there is no null character `\0` in the memory, the behavior is undefined.
<!-- prettier-ignore-end -->

> 供题人:华展辉
> 供题人:华展辉,李英琦
### 「6」 Stack and Queue (adapted from FDS mid-term exam)

Expand Down Expand Up @@ -226,7 +217,7 @@ Please describe what will the program see in `argv` and `argc`.
The `argv` array is terminated by a null pointer, so `argv[5]` is a null pointer.

!!! note inline end ""

![](graph/23.12.04.png)

For more information about `argv` and `argc`, see [cppreference-argc, argv](https://en.cppreference.com/w/c/language/main_function).
Expand Down Expand Up @@ -260,12 +251,12 @@ What is the value of `sizeof(A)` and `sizeof(B)`?

`sizeof(A)` is 16, and `sizeof(B)` is 8 (on a 64-bit modern system).

The `sizeof` operator returns the size of its operand in bytes. The size of a structure type is as large as the sum of the sizes of its members.
The `sizeof` operator returns the size of its operand in bytes. The size of a structure type is as large as the sum of the sizes of its members.

You may think the size of `A` is 14, because the size of `char[10]` is 10, and the size of `int` is 4. However, to improve performance of memory access, C standard allows the compiler to add padding bytes after each member of a structure, and the padding strategy is **implementation specific**. A common strategy is to align each member to the size of its type, that is to say, make their memory address to be a multiple of the size of the member. For example, on a 64-bit system, the address of a 4-byte integer must be a multiple of 4.

Therefore, The size of `A` is 16. The compiler will add 2 padding bytes after `char[10]`, to make the address of `int` a multiple of 4 in `struct A`.

The size of `B` is 8, because `B` is a pointer and the size of pointer is 8. The size of type to which `B` points does not affect the size of `B`. Notice that type `B` is not equivalent to type `struct B`.

For more information about `sizeof` operator, see [cppreference-sizeof](https://en.cppreference.com/w/c/language/sizeof).
Expand Down Expand Up @@ -314,7 +305,7 @@ Hello\0x0aWorld!
putchar('0');
putchar('x');
putchar(hex_digits[temp >> 4]);
putchar(hex_digits[temp & 0xf]);
putchar(hex_digits[temp & 0xf]);
}
}
return 0;
Expand Down Expand Up @@ -424,7 +415,7 @@ int main(){
5. `list->next`
!!! note inline end ""
![](graph/23.11.30.drawio.svg)
Notice this is a doubly linked list **with a dummy node**. The dummy node is a special node that does not store any data. It is used to simplify the implementation of the linked list. In this case, the dummy node is `list`.
Expand Down Expand Up @@ -695,25 +686,25 @@ free(library[0]);
oo
```
The address pointed to by `library[0]->book[0]` plus `(strlen(library[0]->book[0])-i-1) * sizeof(char)` will be eventually passed to `%.2s` and placed into the output stream.
Thus, when `i=0`, `i=1`, and `i=2`, the strings passed to `%.2s` are "k\0", "ok\0", and "ook\0", respectively. Among these, the length of the third string "ook\0" exceeds 2, so only the first two characters "oo" will be output.
The address pointed to by `library[0]->book[0]` plus `(strlen(library[0]->book[0])-i-1) * sizeof(char)` will be eventually passed to `%.2s` and placed into the output stream.
Thus, when `i=0`, `i=1`, and `i=2`, the strings passed to `%.2s` are "k\0", "ok\0", and "ook\0", respectively. Among these, the length of the third string "ook\0" exceeds 2, so only the first two characters "oo" will be output.
This question involves multiple concepts, primarily focusing on both the proper usage of `typedef` and issues related to pointer output with `char*` in C. The analysis will be divided into two parts: `typedef` and `char*`. If you are already familiar with `typedef`, you can directly skip this section.
??? note "`typedef`"
In the C language, `typedef` is used to create new names for existing data types.
When using `typedef`, errors can occur in the specification of its usage. One common misunderstanding is to interpret `typedef (type_name) (new_name)`, which is correct only in a few cases, such as `typedef int Integer`.
However, the correct understanding should be: if you need to redefine a type, first write the declaration statement of that type: `type variable_name`, then replace `variable_name` with the alias you want, and finally add `typedef` in front of the entire statement.
However, the correct understanding should be: if you need to redefine a type, first write the declaration statement of that type: `type variable_name`, then replace `variable_name` with the alias you want, and finally add `typedef` in front of the entire statement.
For example, after `int array[10]`, where the type of the variable `array` is `int [10]`, you can rename `array` to the alias `IntegerList` and add `typedef` at the front, resulting in `typedef int IntegerList[10]`.
After this, you can directly use the alias `IntegerList` to define variables of type `int [10]`, such as `IntegerList a;`, which is equivalent to decelaration: `int a[10]`.
Returning to the question, let's analyze the two `typedef` statements in the question.
Returning to the question, let's analyze the two `typedef` statements in the question.
The first one creates an alias for a structure variable. If we initially want to declare a variable of type `struct Bookcase*`, we would write it like this:
Expand All @@ -733,21 +724,21 @@ free(library[0]);
This statement means giving an alias, `PtrToBookcase`, to the type `struct Bookcase*`.
The second `typedef` is very similar to the example we mentioned earlier, `typedef int IntegerList[10];`.
The second `typedef` is very similar to the example we mentioned earlier, `typedef int IntegerList[10];`.
It first declares `PtrToBookcase Ptr1[MAX_SIZE]`, then replaces the variable name `Ptr1` with the alias `Lib_data_base`, and adds `typedef` at the front.
Therefore, its meaning is to give an alias, `Lib_data_base`, to the type `PtrToBookcase [MAX_SIZE]`.
Therefore, its meaning is to give an alias, `Lib_data_base`, to the type `PtrToBookcase [MAX_SIZE]`.
Consequently, the subsequent `Lib_data_base library` actually creates an array of `PtrToBookcase`, named `library`, with `MAX_SIZE` elements.
??? note "`char*`"
After understanding `typedef`, let's now explore the issue related to string output in this program.
After understanding `typedef`, let's now explore the issue related to string output in this program.
There are two potentially confusing elements in this code: `%.2s` and `library[0]->book[0]+strlen(library[0]->book[0])-i-1`.
`%.2s` is relatively straightforward: It is used to control the output of strings. `%s` would directly output the characters stored at the memory location pointed to by a `char*` type pointer and all characters in consecutive memory until encountering the '\0' character.
`%.2s` is relatively straightforward: It is used to control the output of strings. `%s` would directly output the characters stored at the memory location pointed to by a `char*` type pointer and all characters in consecutive memory until encountering the '\0' character.
The additional `.2` in `%.2s` is used to limit the length of the output string. If the string length is less than or equal to 2, it will be output normally. If it exceeds 2, only the first two characters will be output.
`library[0]->book[0]+strlen(library[0]->book[0])-i-1` involves operations on a `char*` type pointer.
`library[0]->book[0]` is a `char*` type pointer. Adding `n` to it effectively shifts the pointer to a position `n * sizeof(char)` bytes forward from the current address. Subtracting `n` from it shifts the pointer to a position `n * sizeof(char)` bytes backward from the current address.
`library[0]->book[0]+strlen(library[0]->book[0])-i-1` involves operations on a `char*` type pointer.
`library[0]->book[0]` is a `char*` type pointer. Adding `n` to it effectively shifts the pointer to a position `n * sizeof(char)` bytes forward from the current address. Subtracting `n` from it shifts the pointer to a position `n * sizeof(char)` bytes backward from the current address.
<!-- prettier-ignore-end -->
Expand Down Expand Up @@ -779,11 +770,11 @@ printf("%d\n", y << 1 - 1 > 2 || !(x++ > --y) ? x : y);
* *Comparison:* `<`, `>`
* *Logical OR:* `||`
* *Ternary conditional:* `a ? b : c`

See more about operator precedence at [cppreference.com](https://en.cppreference.com/w/cpp/language/operator_precedence).

Knowing this, let's break down the expression from the innermost to the outermost parts.

1. `1 - 1` evaluates to 0, and `y << 0` has the value 2.
2. `2 > 2` is false, thus the value of the Logical OR depends on the right part.
3. `x++` assigns the value of `x` (1) to the expression and then increments `x` by 1, making `x` equal to 2.
Expand Down Expand Up @@ -825,8 +816,8 @@ D. It cannot be compiled.
`D`.
The compiler will tell you that `case label value has already appeared in this switch`.
There are many problems in this code fragment, and the most important one is that `0 || 2 || 4 || 6 || 8` will not behave as expected in `case` statement. It will be evaluated as `1` because `0 || 2 || 4 || 6 || 8` is equivalent to `((((0 || 2) || 4) || 6) || 8)`. The result of `0 || 2` is `1`, so the result of `0 || 2 || 4` is `1`, and so on. Therefore, the `case` statement will be evaluated as `case 1`. The same problem exists in `case 1 || 3 || 5 || 7 || 9`, which will also be evaluated as `case 1`.
The compiler will tell you that `case label value has already appeared in this switch`.
There are many problems in this code fragment, and the most important one is that `0 || 2 || 4 || 6 || 8` will not behave as expected in `case` statement. It will be evaluated as `1` because `0 || 2 || 4 || 6 || 8` is equivalent to `((((0 || 2) || 4) || 6) || 8)`. The result of `0 || 2` is `1`, so the result of `0 || 2 || 4` is `1`, and so on. Therefore, the `case` statement will be evaluated as `case 1`. The same problem exists in `case 1 || 3 || 5 || 7 || 9`, which will also be evaluated as `case 1`.
The correct way to write this code fragment is:
```c
Expand Down Expand Up @@ -865,7 +856,7 @@ D. `(a[x], a[y])`: `a[x] -= a[y] += a[x] -= a[y];`
`A`.
- `B` will always be `0` when `x==y`.
- `B` will always be `0` when `x==y`.
- `C` and `D` is not logically correct.
<!-- prettier-ignore-end -->
Expand Down Expand Up @@ -980,11 +971,11 @@ for (x = 0; x-- < 9 || ++x < 10; ++x) {
2. Logical OR `||`

The logical OR expression has the form `lhs || rhs`, in which `rhs` is only evaluated if `lhs` compares equal to `​0`​.

For more information, see [cppreference-Logical operators](https://en.cppreference.com/w/c/language/operator_logical)

When `x < 9`, each loop will cause `x` to increase by `1`. Note that only `x-- < 9` is evaluated in each loop now.

Now `x` is equal to `9` before the cond-expression of the for loop. First, `x-- < 9` is evaluated, which compares equal to `0`, and causes `x` to decrease by `1`. Then `x` is equal to `8` and `++x < 10` is evaluated, which compares equal to `1` and causes `x` to increase by `1`. Loop continues.

Then, `x` is equal to `11` before the cond-expression of the for loop. Now `x-- < 9` and `++x < 10` both compare equal to `0`, so the loop ends. `x` firstly decreases by `1` and then increases by `1`, so the final value of `x` is `11`.
Expand Down Expand Up @@ -1037,7 +1028,7 @@ else
In C (and many other programming languages), [floating-point](https://zh.wikipedia.org/wiki/%E6%B5%AE%E7%82%B9%E6%95%B0) arithmetic is not always exact due to the way numbers are represented in binary. This means that sometimes, tiny errors can be introduced and accumulated when performing mathematical operations on floating-point numbers.

In this case, when adding 0.1 and 0.2 together, the result is not precisely 0.3 due to these inaccuracies. Thus, directly comparing floating-point numbers with `==` can lead to unexpected results.

To deal with such issues in real programs, check if the difference between two floating-point numbers is smaller than a tiny threshold.

For more information about floating-point numbers, search for [IEEE 754](https://zh.wikipedia.org/wiki/IEEE_754).
Expand Down

0 comments on commit ed252ce

Please sign in to comment.