Skip to content

2023-2024

为了让同学们适应期末考试,每日一题的题面都将使用英文描述。

December

「10」 I Love pointer

What is the output of the following program on a 64-bit system?

    char str[3][10]={"","ILoveCKC","ILoveZJU"};
    printf("%lu#%lu\n",strlen(str[0]),sizeof(str[0]));
    printf("%lu#%lu\n",strlen(str),sizeof(str));
    printf("%d#%c\n",str[1][-10],str[1][10]); // Don't write this in your own code.
    printf("%s\n",(**str == (*(str + 1) + 8)[1] ? "True" : "False"));
    printf("%s",&str[1][9] == &((*(str + 1) + 8)[1]) ? "True" : "False");
Answer
    0#10
    0#30
    0#I
    True
    True
  1. strlen(str[0]) returns the length of the string pointed to by str[0], which is 0. sizeof(str[0]) returns the size of the array str[0], which is 10.
  2. strlen(str) returns the length of the string pointed to by str, which is 0. sizeof(str) returns the size of the array str, which is 30. In fact, str[0]'s value is same as str's value.
  3. str[1][-10] is equivalent to *(str[1] - 10) which is equivalent to **(str + 1 * 10 - 10) which is equivalent to ** str. So str[1][-10]'s value is '\0'. str[1][10] is equivalent to *(str[1] + 10) which is equivalent to **(str + 1 * 10 + 10) which is equivalent to ** (str + 20) So str[1][10]'s value is 'I'.
  4. (*(str + 1) + 8)[1] is the same as str[1][9]. So **str == (*(str + 1) + 8)[1] is true.

供题人:胡育玮

「9」 sizeof All in One

What is the output of the following program on a 64-bit system?

char str[][3] = {'L', 'o', 'v', 'e', 'C', 'K', 'C'};
printf("%d ", (int)sizeof(str));
printf("%d ", (int)sizeof(&str));
printf("%d ", (int)sizeof(*str));
printf("%d ", (int)sizeof(str + 1));
printf("%d ", (int)sizeof(*str + 1));
printf("%d ", (int)sizeof(*(str + 1)));
printf("%d", (int)sizeof(*(*(str + 1) + 1)));
Answer

9 8 3 8 8 3 1.

This is a comprehensive problem combining sizeof and pointer.

  1. When the object is an array name, the return value is the total size of the whole array. At initialization, the third row of the array will be filled to a 1-D array of length 3 automatically: char str[][3] = {{'L', 'o', 'v'}, {'e', 'C', 'K'}, {'C', '\0', '\0'}};. Therefore the value of sizeof(str) equals to 3 * 3 * 1 = 9 since sizeof(char) = 3.
  2. In the expression &str, & represents the address-of operator and the return value is the address of the entire array. That, of course, can be regarded as a pointer and hence sizeof(&str) equals 8 (Bytes), the size of a pointer on a 64-bit system.
  3. Now let's focus on str. We know that str is an array of char [3]. In the view of pointer, str is also a pointer to the first element (Note that the element here is an array of length 3). Hence *str is equivalent to str[0] which is an 1D array of length 3. So the output of sizeof(*str) is 3 * 1 = 3.
  4. In the previous discussion we know that str is a pointer to array of length 3 (char (*)[3] actually), so str + 1 is also a pointer and the output is 8.
  5. *str (equivalent to str[0]), will be converted to int * when added by 1. Therefore *str + 1 is a pointer to str[0][1] and 8 will be output again.
  6. From discussion 3 and 4 it's not difficult to find *(str + 1) an array of length 3 (str[1]), so 3 is the answer.
  7. *(*(str + 1) + 1) is classical equivalence of str[1][1]. Therefore sizeof(*(*(str + 1) + 1)) equals to sizeof(char), namely 1.

供题人:徐若禺

「8」 J lost

Given the following program:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void xqc(char c[], int i);

int main(void) {
    char c[] = "I am a Man of Fortune";
    char d[] = "and I must seek my Fortune";

    xqc(c + 1, ~1694);
    xqc(d - ~3, -65);
    printf("%s, %s\n", c, d);
}

void xqc(char c[], int i) {
    c = c - 1;
    c[0] = ' ';
    c[1] = (i & 1) + 'I';
}

Which of the following is correct?

A. This program fails to compile, because you cannot assign to an array in line 13.

B. Because of the call-by-value, function xqc cannot modify the character arrays in function main.

C. Change 1694 in line 7 to 1994, and the result of the program is the same.

D. This program outputs I am a Man of Fortune, and J must seek my Fortune.

Answer

C.

A: Inside function xqc, c is a pointer type char * rather than an array type, because when an array type is used in a function parameter list, it actually is the corresponding pointer type. Therefore, the assignment in line 13 is valid, and it means "to move the pointer c one position backward".

B: The function xqc can modify the character arrays in function main, because it takes the address of the character arrays as parameters. The function xqc can modify the contents of the character arrays through the pointers.

C: The ~ operator is the bitwise NOT operator. The ~1694 is equivalent to -1695. The ~3 is equivalent to -4. Therefore, the calls to function xqc are equivalent to:

xqc(c + 1, -1695);
xqc(d - -4, -65);

And i & 1 gets the last bit of i. Both -1695 and -65 are odd numbers, so i & 1 is 1, and the character 1 + 'I', which is 'J', is assigned. Changing 1694 to 1994 does not change the result of the program, because ~1994 is -1995, and -1995 is also an odd number.

D: This program outputs  Jam a Man of Fortune, and J must seek my Fortune.

Tip

You don't need to calculate the exact value of ~1694. All you need to know is that the last bit of 1694 is 0 (since it is an even number), and the bitwise NOT operator will reverse that last bit. Therefore, ~1694 is an odd number, and i & 1 is 1. Ditto for ~1994.

供题人:李英琦

「7」 The difference between strlen and sizeof

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>
#include <string.h>
int main()
{
 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;
}

What are the results?

A. 4, 4, 4, 4
B. 5, 4, 4, 4
C. 4, 4, 4, indeterminable
D. 5, 4, 4, indeterminable
Answer

D.

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

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.

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.

供题人:华展辉,李英琦

「6」 Stack and Queue (adapted from FDS mid-term exam)

Given an empty stack S and an empty queue Q. Push elements {1, 2, 3, 4, 5, 6, 7} one by one onto S. If each element that is popped from S is enqueued onto Q immediately, and if the dequeue sequence is {3, 2, 4, 6, 7, 5, 1}, then the minimum size of S should be:

A. 5
B. 4
C. 3
D. 7
Answer

C.

Stack is LIFO (Last In, First Out), queue is FIFO (First In, First Out). So dequeue sequence of queue is also the enqueue sequence. If enqueue sequence of Q is {3, 2, 4, 6, 7, 5, 1}, all operation of stack S must be the following:

  • Push 1 and 2 and 3 into stack sequentially. Stack S now contains 3 nodes.
  • Pop 3, stack contains 2 nodes
  • Pop 2, stack contains 1 node
  • Push 4, stack contains 2 nodes
  • Pop 4, stack contains 1 node
  • Push 5, stack contains 2 nodes
  • Push 6, stack contains 3 nodes
  • Pop 6, stack contains 2 nodes
  • Push 7, stack contains 3 nodes
  • Pop 7, stack contains 2 nodes
  • Pop 5, stack contains 1 node
  • Pop 1, stack is empty

In the process, there are at most 3 nodes in stack S, so the minimum size of S should be 3.

供题人:姚雪涛

「5」 Monotonic stacks

Monotonic stacks are a specialized version of the standard stack data structure, designed to maintain elements in a pre-defined sorted order. Unlike regular stacks, which allow push and pop operations without any constraints, monotonic stacks enforce an order - either increasing or decreasing - on the elements. This means that elements are either strictly increasing or strictly decreasing from the top to the bottom of the stack.

The core operations of a monotonic stack, namely push, are modified to maintain the stack’s order. During a push operation, elements that break the monotonic property are removed from the stack before the new element is added.

For example, now we have a decreasing monotonic stack, where elements closer to the top have the smaller values:

[5,4,2,1]
       ^
       |
       top

We push a data with the value \(3\), and because \(1,2\) is smaller than \(3\), we remove them. And finally it will be:

[5,4,3]
     ^
     |
     top

Now, your task is to create an decreasing monotonic stack in the C language.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
typedef struct Node {
    int data;
    struct Node* next;
} Node;

typedef struct Stack {
    Node* top;
} Stack;

Stack* createStack() {
    Stack* stack = (Stack*)malloc(sizeof(Stack));
    /* (1) */;
    return stack;
}

void push(Stack* stack, int data) {
    while (/* (2) */) {
        Node* temp = stack->top;
        stack->top = temp->next;
        /* (3) */;
    }
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    /* (4) */;
    stack->top = newNode;
}

int pop(Stack* stack) {
    if (isEmpty(stack)) {
        printf("Stack is empty\n");
        return -1;
    }
    Node* temp = stack->top;
    int popped = temp->data;
    stack->top = temp->next;
    free(temp);
    return popped;
}
Answer

单调栈是一种特殊的栈,其中每个元素都严格小于或大于它下面的元素。大家在后续的课程中有可能会接触到他的一些应用。接下来我们逐一看看这些题目怎么填写:

第一空:这里需要初始化栈顶元素。由于栈是空的,所以栈顶应该设置为 NULL

stack->top = NULL;

第二空:这是最难的一空。根据题意,这一层循环的目的是:移除所有小于或等于新元素的栈顶元素,以维持栈的单调性。但是还有一种情况:如果栈本身是空的,或者所有元素都比新元素小,那必须得退出循环。

while (stack->top != NULL && stack->top->data <= data)

第三空:不大容易想到。在移除栈顶元素后,应该释放该节点占用的内存。

free(temp);

为什么出这个?因为 wk 明确说过,考试考这个填空,不写 free 就少 2 分,所以出了这个空来让大家注意。

第四空:这里需要将新节点的 next 指针指向之前的栈顶元素,然后将栈顶指针指向新节点,这样新节点就成为了新的栈顶。

newNode->next = stack->top;

供题人:谢集

「4」 Command Line Arguments

If you run the following command in the terminal:

1
./a.out this is a test

Please describe what will the program see in argv and argc.

Answer

argv is an array of pointers to strings, and argc is the number of strings in argv. The argv array contains the following strings:

argv[0] = "./a.out"
argv[1] = "this"
argv[2] = "is"
argv[3] = "a"
argv[4] = "test"

The argc is 5, because there are 5 strings in argv.

The argv array is terminated by a null pointer, so argv[5] is a null pointer.

For more information about argv and argc, see cppreference-argc, argv.

供题人:朱宝林

「3」 sizeof struct

Consider the following code fragment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct A {
    char a[10];
    int b;

};
typedef struct A A;

struct B {
    char *a;
    int b;
};
typedef struct B* B;

What is the value of sizeof(A) and sizeof(B)?

Answer

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.

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.

供题人:朱宝林、孙兆江

「2」 Print Non-printable Characters

Write a program to convert unprintable characters (characters with ASCII codes between 0x00~0x1f and 0x7f) in the input string to hexadecimal format for output.

Requirements:

  • Do not use the printf series of formatting output functions.
  • The minimum field width for output is 2, and if the hexadecimal number is less than 2 digits, pad it with leading zeros. For example, 0x0a.

Sample Input:

Hello
World!

Sample Output:

Hello\0x0aWorld!
Answer
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <ctype.h>

const char* hex_digits = "0123456789abcdef";

int main(void) {
    char temp;
    while((temp = getchar()) != EOF) {
        if (isprint(temp)) {
            putchar(temp);
        } else {
            putchar('\\');
            putchar('0');
            putchar('x');
            putchar(hex_digits[temp >> 4]);
            putchar(hex_digits[temp & 0xf]);
        }
    }
    return 0;
}

If you are not familiar with bitwise operations, the 14th and 15th lines in the above code fragment can be written in a more understandable form:

putchar(hex_digits[temp / 16]);
putchar(hex_digits[temp % 16]);

供题人:朱宝林

「1」 Hello, Cat

After executing the following code fragment, the output should be __.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>
#include <string.h>
int main()
{
    char s1[10] = "Hello\0Ow<";
    char s2[10] = "world";
    char *s3 = s1 + 2;
    char *s4 = s2 + 3;
    strcat(s3, s4);
    printf("%s, %s; %s, %s.\n", s1, s2, s3, s4);
    return 0;
}

A. Hello, world; llold, ld.

B. Hello, world; lloldw<, ld.

C. Hellold, world; llold, ld.

D. Helloldw<, world; lloldw<, ld.

Answer

C.

The strcat function appends a copy of the string pointed to by s4 to the end of the string pointed to by s3. The s3 and s4 pointers point to the third character of s1 and the fourth character of s2, respectively. Therefore, the strcat function appends the string "ld" to the end of the string "Hellold", resulting in "Helloldld", and the printf function prints "Hellold, world; llold, ld.".

When the strcat function appends the string "ld" after "Hello", it overwrites the null character '\0' at the end of the string "Hello" and write a new null character '\0' at the end of the string "Helloldld". Therefore, the character w and < after the null character '\0' in s1 are not printed.

供题人:郑俊达

November

「30」 Basic Doubly Linked List

Please fill in the blanks to complete the following code fragment.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node *prev, *next;
} Node;
typedef struct Node *List;

List createEmptyList() {
    List list = (List)malloc(sizeof(Node));
    list->prev = list->next = NULL;
    return list;
}

void insertFront(List list, int data) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->data = data;
    node->prev = /* 1 */;
    node->next = /* 2 */;
    list->next->prev = /* 3 */;
    list->next = /* 4 */;
}

int main(){
    List list = createEmptyList();
    insertFront(list, 1);
    insertFront(list, 2);
    insertFront(list, 3);
    for (Node *node = /* 5 */; node != NULL; node = node->next)
        printf("%d ", node->data);
    return 0;
}
Answer
  1. list
  2. list->next
  3. node
  4. node
  5. list->next

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.

The insertFront function inserts a new node with data at the front of the list. The new node is inserted between the dummy node and the first node of the list. The insertFront function takes two steps:

  1. Create a new node and set its data to data.
  2. Insert the new node between the dummy node and the first node of the list.

If you want to iterate over the list, you should start from the first node of the list, which is list->next.

供题人:朱宝林

「29」 Soyo Size

On a 64-bit machine, the output of the following code fragment is __.

1
2
3
4
5
6
7
8
9
void print_soyo(char soyo[]) {
    printf("%lu\n", sizeof(soyo));
}

int main() {
    char soyo[] = "Soyosan\0Love";
    print_soyo(soyo);
    return 0;
}
A. 13
B. 7
C. 8
D. 4
Answer

C.

When an array type is used in a function parameter list, it is transformed to the corresponding pointer type: int f(int a[]) and int f(int *a) declare the same function. See cppreference-Array to pointer conversion for more information.

So the parameter soyo in function print_soyo is actually a pointer, and the size of a pointer is variable depending on the architecture. On a 64-bit machine, the size of a pointer is 8 bytes, and on a 32-bit machine, the size of a pointer is 4 bytes. Therefore, the output of this program is 8.

供题人:苏煜程

「28」 Naughty Pointer

The following code fragment prints out __.

1
2
3
int a[2][3] = {1, 2, 3, 4, 5, 6};
int(*p)[3] = *a + 1;
printf("%d", *(*(p + 1) + 1));
Answer

6.

Obviously, the type of p is declared as int (*)[3], which is a pointer to an array of 3 integers. We know that in the initializer, a will be converted to a pointer to its first element, so *a is equivalent to a[0], which is an array of 3 integers. Then, the type of *a will be converted from int [3] to int * again. Therefore, adding 1 to *a will make it point to the next integer, which is a[0][1].

You may notice that the type of *a + 1 is not the same as pointer p. In fact, its type is casted to int (*)[3] when assigned to p. Trying to figure out the "meaning" of p now may become more difficult. Instead, just remember the value of pointer p is the address of a[0][1].

Now consider the expression *(*(p + 1) + 1). First, since the type of p is int (*)[3], adding 1 to p will make it point to the next array of 3 integers; that is to say, the value of p will be the address of the third integer after a[0][1], which is a[1][1]. So *(p + 1) is just equivalent to &a[1][1]. Then, adding 1 to *(p + 1) will make it point to the next integer, which is a[1][2]. Thus, the value of *(*(p + 1) + 1) is a[1][2], which is 6.

供题人:孙兆江

「27」 I Love scanf

After entering the following inputs, does the program operate normally? If it does, what should be the output?

1
2
3
4
5
int a;
char c1, c2;
char s[7];
scanf("%d%c %c%s\n", &a, &c1, &c2, s);
printf("%d#%c#%c#%s", a, c1, c2, s);
1
2
3
4
#input: there is no space after the last character
20231127
 ckc-agc
daily_problem
Answer

The program operates normally and the output is:

20231127#
#c#kc-agc

The scanf function reads input from the standard input stream, which is usually the keyboard. The format string of scanf is "%d%c %c%s\n". The first %d matches the integer 20231127, the second %c matches the character '\n' because %c won't miss any character including ' ' and '\n'. The space in formatting string will ignore every blank character, so the third %c matches the character 'c', and the fourth %s matches the string "kc-agc", whose length is 7. When you print \n after line2, scanf will not stop, because '\n' in formatting string will ignore every blank character. So until you enter a non-blank character and use enter to send it to the program from buffer, scanf will stop.

供题人:胡育玮

「26」 Broken strcpy()

Does this strcpy() implementation do its job?

1
2
3
4
5
void my_strcpy(char *s, const char *t) {
    do {
        *s++ = *t++;
    } while (*t);
}
Answer

No, it's broken. This implementation does not terminate the string pointed to by s. To correctly copy a string, the character used in assignment must be the same character used in loop condition.

What does that mean? Suppose t = "Hello", then:

  • *t is 'H', and is assigned to *s.
  • s and t increment.
  • *t, now being 'e', is used to evaluate the while condition.
  • ... this process repeats until t points to 'o' and is assigned to *s.
  • s and t increment.
  • *t, now being '\0', is used to evaluate the while condition. The loop ends.

See the problem? The null byte '\0' is not copied to s, therefore it's left unterminated. Any attempt to use s as a string will result in undefined behavior.

Worse still, what happens if t is an empty string i.e. t = ""?

  • '\0' is assigned to *s.
  • s and t increment. t gets past its end.
  • Now we have a memory out-of-bounds. Accessing *t is undefined behavior.
  • And it keeps copying garbage values until some arbitrary '\0', or it goes too far and causes a segmentation fault.

供题人:李英琦

「25」 I Love Strcat

The following code fragment prints out __.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void my_strcat(char *s1, char *s2, char *s3) {
    size_t len1 = strlen(s1);
    size_t len2 = strlen(s2);
    s3 = malloc(len1 + len2 + 1);
    memcpy(s3, s1, len1);
    memcpy(s3 + len1, s2, len2);
    s3[len1 + len2] = 0;
    printf("%s\n", s3);
}

int main() {
    char s1[] = "I love ";
    char s2[] = "cats!";
    char s3[] = "";
    my_strcat(s1, s2, s3);
    printf("%s\n", s3);
}

A. I love cats!\n\n

B. \n\n

C. \nI love cats!\n

D. I love cats!\nI love cats!\n

Answer

A.

The function my_strcat is supposed to concatenate s1 and s2 and store the result in s3. However, the function does not work as expected. The problem lies in the line s3 = malloc(len1 + len2 + 1);. The function my_strcat takes s3 as a parameter, which is a pointer to a char array. When the function is called, the value of s3 is copied to the function's local variable s3. Therefore, the malloc function allocates memory for the local variable s3, not the original s3 in main. The memory allocated for the local variable s3 is not used after the function returns, so it is a memory leak. The original s3 in main is not modified, so it is still an empty string.

You may think that the problem can be solved by simply deleting the line s3 = malloc(len1 + len2 + 1);. However, this will cause another problem. s3 is initialized as an empty string, which means that it is a pointer to a char array with only one element, the null character \0. The memory allocated for s3 is not enough to store the concatenated string. Therefore, this will cause a buffer overflow.

供题人:梅敏炫

「24」 Pointer Array

The output of the following code fragment is __.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
 char* a[] = { "the","one","is","unknown" };
 char** pa = a;
 pa++;
 printf("%c%s\n", *(*pa + 1), *pa);
 return 0;
}
A. none
B. tone
C. tthe
D. nis
Answer

A.

a[] is an array of pointers, which stores the addresses of three strings, and pa points to the address of the first element of a, as shown below:

Pointer operations take precedence over addition, so *(*pa + 1) equals to n.

In summary, the code fragment prints out none.

供题人:华展辉

「23」 typedef and String

The preprocessing of this program is as follows:

1
2
3
4
5
6
7
#define MAX_SIZE 10000

typedef struct Bookcase{
    char* book[10];
} *PtrToBookcase;

typedef PtrToBookcase Lib_data_base[MAX_SIZE];

After executing the following code fragment, the output should be __.

1
2
3
4
5
6
Lib_data_base library;
*(library) = (PtrToBookcase)malloc(sizeof(struct Bookcase));
(*library)->book[0] = "C Programming Book";
for(int i = 0; i < 3; i++)
    printf("%.2s\n", library[0]->book[0] + strlen(library[0]->book[0]) - i - 1));
free(library[0]);
Answer

Output is:

k
ok
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.

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.

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.

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.

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:

struct Bookcase {
    char* book[10];
} *Bookcase1;

Following the rules of typedef, replace the variable name Bookcase1 with the alias PtrToBookcase and add typedef at the beginning of the entire statement, resulting in the form seen in the question:

typedef struct Bookcase {
    char* book[10];
} *PtrToBookcase;

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];. 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]. Consequently, the subsequent Lib_data_base library actually creates an array of PtrToBookcase, named library, with MAX_SIZE elements.

char*

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. 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.

供题人:姚雪涛

October

「29」 Precedence

The output of the following code fragment is __.

1
2
int x = 1, y = 2;
printf("%d\n", y << 1 - 1 > 2 || !(x++ > --y) ? x : y);
Answer

The answer is 2.

Several operators appear in this problem, listed in order of operation precedence from top to bottom:

  • Suffix increacement and decreacement: a++
  • Logical NOT: !
  • Prefix increacement and decreacement: --a
  • Subtraction: -
  • Bitwise left shift: <<
  • Comparison: <, >
  • Logical OR: ||
  • Ternary conditional: a ? b : c

See more about operator precedence at cppreference.com.

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.
  4. --y decreases the value of y (2) by 1, so y becomes 1.
  5. x++ > --y is equivalent to 1 > 1, which is false, so this part equals 0.
  6. !(x++ > --y) negates false, so this part equals 1, making the condition of the Ternary conditional true.
  7. Since x has the value of 2 and y has the value of 1, the output will be 2.

供题人:徐若禺

「27」 Or in switch

What will happen when compiling and executing the following code fragment with input 5?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
char ch;
scanf("%c", &ch);
switch (ch) {
    case 0 || 2 || 4 || 6 || 8:
        printf("even digit ");
    case 1 || 3 || 5 || 7 || 9:
        printf("odd digit ");
    default:
        printf("not a digit ");
}

A. It will print odd digit.

B. It will print odd digit not a digit.

C. It will print not a digit.

D. It cannot be compiled.

Answer

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 correct way to write this code fragment is:

char ch;
scanf("%c", &ch);
switch (ch) {
    case '0': case '2': case '4': case '6': case '8':
        printf("even digit "); break;
    case '1': case '3': case '5': case '7': case '9':
        printf("odd digit "); break;
    default:
        printf("not a digit ");
}

For more information about switch statement, see cppreference-Switch statement.

供题人:郑俊达

「26」 Bitwize Operator

Which of the following options can achieve a swapping effect for pair(*,*)? Note that ^ represents XOR operation. For binary numbers, 0 XOR 0 = 0, 0 XOR 1 = 1, 1 XOR 0 = 1, 1 XOR 1 = 0.

A. (x, y): x ^= y ^= x ^= y;

B. (a[x], a[y]): a[x] ^= a[y] ^= a[x] ^= a[y];

C. (x, y): x -= y += x -= y;

D. (a[x], a[y]): a[x] -= a[y] += a[x] -= a[y];

Answer

A.

  • B will always be 0 when x==y.
  • C and D is not logically correct.

供题人:程昕宇

After executing the following code fragment, the output should be __.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int ok[50000];
for (int i = 0; i <= 19268; ++i) ok[i] = 1;
for (int i = 19269; i <= 49999; ++i) ok[i] = 0;
int l = 0, r = 49999;
while (l < r){
    int mid = (l + r) / 2;
    if (ok[mid]) l = mid;
    else r = mid - 1;
}
printf ("the ok_maximum is %d", l);
Answer

In fact, this code fragment has no output because it is stuck in an endless loop. Let's see what happens: At first the code executes perfectly, l increasing and r decreasing constantly. However, when the value of r-l reduce to 1, the value of l and r will never change again. That's because mid equals to l and ok[mid] is true (Think about it. why?), so l = mid will be execute, again and again with no value change.

Binary Search is a very simple, common and useful algorithm that you will learn soon. However, when using Binary Search, it is easy to write a wrong code. It is said that only 10% of the programmers can write a exactly correct code. Hence, you need to pay special attention to this algorithm. A small change can possibly change the code correctness. For example, modifying the int mid = (l + r) / 2; to int mid = (l + r + 1) / 2; makes the code correct.

供题人:郑涵文

「24」 Nested switch, Confusing default and break

After executing the following code fragment, the output should be __.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int x = 1, y = 1;
switch (x) {
    case 1:
        switch (y) {
            default:
                printf("three ");
            case 1:
                printf("one ");
            case 2:
                printf("two ");
                break;
        }
    case 2: case 3:
        printf("four ");
        break;
    default:
        printf("five ");
}
Answer

one two four

The syntax of switch statement is:

switch (expression) {
    case constant-expression:
        statements
    case constant-expression:
        statements
    default:
        statements
}

The expression must have an integral or enumerated type, or be of a class type in which the class has a single conversion function to an integral or enumerated type. The constant-expression for each case must be a constant expression of the same type as the expression, and no two of the constant expressions associated with the same switch statement shall have the same value after conversion.

The switch statement evaluates expression, then looks for the case constant-expression whose value is equal to the value of expression (after conversion). If such a case is found, the statements following that case are executed until a break statement is encountered. If no case is found whose value is equal to the value of the expression, and if there is a default label, the statements following the default label are executed. Otherwise, the statements of the switch statement are skipped.

If some case labels are not followed by break statements, the execution of the switch statement falls through to the next case label. You can use this behavior to execute multiple statements for a particular case label or to execute statements for more than one case label.

供题人:朱宝林

「23」 Prefix OR Postfix

After executing the following code fragment, the value of variable x is __.

1
2
3
4
int x;
for (x = 0; x-- < 9 || ++x < 10; ++x) {
    x++;
}
Answer

11

  1. The result of the increment and decrement operators

    Increment and decrement operators have postfix form x++ (or x--), and prefix form ++x (or --x).

    • The result of the postfix forms is the value of x.
    • The result of the prefix forms is the value of x + 1 for ++x (or x - 1 for --x).

    For more information, see cppreference-Increment/decrement operators

  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

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.

供题人:苏煜程

「22」 String Comparison

What's wrong with this code snippet?

1
2
3
4
5
6
7
8
char a[] = {"abcd"};
char b[] = {"abcd"};
char c[] = {"ABCD"};

if(a > b) printf("[1] YES\n");
else printf("[1] NO\n");
if(a > c) printf("[2] YES\n");
else printf("[2] NO\n");
Answer

Actually, the output of this program is unpredictable.

  1. To compare two strings, you should use strcmp function in <string.h> header file. Learn how to use the function by yourself.
  2. In the code snippet above, a, b and c are all arrays of char, so a > b and a > c are actually comparing the addresses of these arrays. You may think that the array declared later has a larger address, but this is not always true. It depends on the design of the architecture that the stack grows towards higher memory addresses or towards lower memory addresses. So the output of this program is unpredictable. For more information on this, see StackOverflow: Is the order of memory addresses of successively declared variables always descending?

供题人:杨琳玥

「21」 Addition

After executing the following code fragment, the output should be __.

1
2
3
4
5
double a = 0.1, b = 0.2;
if (a + b == 0.3)
    printf("Equal.\n");
else
    printf("Not equal.\n");
Answer

Not equal.

In C (and many other programming languages), floating-point 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.

供题人:孙兆江

「20」 Bad for

After executing the following code fragment, the output should be __.

1
2
3
4
unsigned int x , y; // 'int' occupies 4 bytes
for (x = 2, y = 1; x = y; y++)
    ;
printf("%d", x-1);
Hint

Review Oct.「18」 & 「16」 may help you get right answer.

Answer

-1.

Notice that the condition of for is x = y, not x == y. So for loop will terminate until y = 0. You should consider overflow in unsigned int variable.

When we execute ++ for unsigned int y in for loop, we will get 0x111...1(\(2^{32}-1\)) as the max unsigned int, and then we will get 0x00...0 as 0 because of overflow. So x gets the value of y(0) and terminates for. In printf, x-1 is 0x11...1. When you use %d to print, it will be considered as a signed interger, so the answer is -1, not \(2^{32}-1\).

供题人:胡育玮

「19」 IsPrime?

What's wrong with this code snippet?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int d;
scanf("%d", &d);

// determine if d is a prime number
for (int i = 2; i <= d; i++) {
  if (d % i == 0) {
    printf("%d is a composite\n", d);
    exit(0);
  }
}
printf("%d is a prime\n", d);
Answer

Notice the condition of the for loop: i <= d, which means i will eventually be equal to d. Therefore d % i == 0 evaluates to 1, reporting composite for every input greater than 1.

供题人:李英琦

「18」 Bad if-else

After executing the following code fragment, the output is __.

1
2
3
4
5
6
int x = 0;
int y = -2;
if(x > 0) y = 1;
else if(x < 0) y = -1;
else if(x = 0) y = 0;
printf("%d", y);
A. 1
B. 0
C. -1
D. -2
Answer

D.

Notice that the condition of if in line 5 is x = 0, not x == 0. The former means "0 is assigned to x", and then the if sentence won't be executed since it is equivalent to if(0). Thus the value of y is its initial value, -2.

供题人:梅敏炫

「17」 Character

Given: char c = '@';, the value of expression 'A' <= c <= 'Z' is __.

Answer

1.

To solve this problem you do not need to remember ASCII code. This problem is about operator precedence. The expression 'A' <= c <= 'Z' is equivalent to (('A' <= c) <= 'Z'). No matter what the value of c is, the expression ('A' <= c) will be evaluated to 1 or 0, which is less than 'Z'. Therefore, the value of the expression 'A' <= c <= 'Z' is 1.

But in other problems you may need to remember some key ASCII codes:

Character ASCII Code
'0' 48
'A' 65
'a' 97

Upper case is prior to lower case. The difference between 'A' and 'a' is 32. So if you want to convert a lowercase letter to uppercase, you can use c - 32. Or an easier way: c - 'a' + 'A'.

来自:C 13 年期末

「16」 Overflow

For code below:

1
2
unsigned short sht = 0;
sht--;

What will be the value of sht after the execution?

A. -1
B. 65535
C. 32767
D. 65536
Answer

B.

Remember that unsigned short is an unsigned integer type, so it will not be negative. Instead, it will be the largest value of unsigned short type. The largest value of unsigned short type is 65535, because unsigned short is 16-bit long.

来自:C 13 年期末