C 语言小测验

Mike Ash Friday Q&A 中文译文:C 语言小测验

作者 TommyWu
封面圖片: C 语言小测验

译文 · 原文: Friday Q&A 2013-05-31: C Quiz · 作者 Mike Ash

原文:https://www.mikeash.com/pyblog/friday-qa-2013-05-31-c-quiz.html 发布:2013-05-31 作者:Mike Ash 译者:MiMo(mimo-v2.5-pro);代码块保留英文原样


今天我想换个口味,给读者们来个小测验。C 语言或许是现存最流行的计算机语言,但它也相当怪异,正因如此常常被误解。我想通过一个小测验,看看大家对这门语言中那些奇异却实用的角落了解多少。

问题

以下是题目。答案随后奉上。请在阅读答案前先自行尝试解答所有问题。建议先凭记忆作答,然后可以使用编译器、语言规范或其他任何工具来核对答案。但请记住,通过编译器测试得出的答案可能只反映你的特定环境,并不一定普遍正确。

  • 字符字面量 'a' 的类型是什么?

  • 表达式 a == b 的类型是什么?

  • 表达式 1 == 1 的值是什么?那么 0 == 1 呢?

  • 表达式 42 || 0 的值是什么?

  • 表达式 -1 < 1 的值是什么?

  • 表达式 -1 < 1U 的值是什么?

  • 假设一个局部变量声明为 char a[10],那么 sizeof(a) 的值是多少?

  • 给定一个声明为 void f(char a[10]) 的函数,在函数体内部 sizeof(a) 的值是多少?

  • UINT_MAX + 1 的值是多少?

  • INT_MAX + 1 的值是多少?

  • NULL 的类型是什么?

  • 对于类型 charshortintlonglong longsizeof 的结果是多少?

  • 使用 printf 打印 int 的格式说明符是什么?

  • 使用 printf 打印 short 的格式说明符是什么?

  • 使用 printf 打印 double 的格式说明符是什么?

  • 使用 printf 打印 float 的格式说明符是什么?

  • 表达式 *(char *)NULL 的值是多少?

  • 字符串字面量 "abcde" 的类型是什么?

  • sizeof("abcde") 的值是多少?

  • 执行 free(NULL) 的结果是什么?

  • 执行 realloc(NULL, sizeof(int)) 的结果是什么?

间歇 让我们给那些在阅读上面几个问题时眼神游离的人一点空间。

我想需要比那再稍微多一点的空间。

就再多一两段吧。

你有没有试着自己找出答案?在继续往下读之前,你真应该试一试。

最后机会!答案就在下面。

  1. 字符字面量 'a' 的类型是什么? 这个很简单。它是一个字符字面量,所以它的类型是 char。对吧?

不。其类型是 int。我不完全确定原因,但标准就是这么说的。值仍然是你所预期的,因此实际影响很小。通常,只有当你尝试在其中使用 sizeof 时才会注意到这一点。

  1. 表达式 a == b 的类型是什么?对于来自更合理语言的开发者来说,自然会认为是 BOOL,或 bool,甚至可能是 C99 标准中 C 语言官方内置的布尔类型 _Bool。然而,事实并非如此:比较表达式(comparison expressions)的类型始终是 int

  2. 表达式 '1 == 1' 的值是什么?那 '0 == 1' 呢?尽管类型总是 int,但至少常识占了上风:当比较为真时值为 1,为假时值为 0。这里的答案分别是 1 和 0。

  3. 表达式 42 0 的值是什么? 运算符(逻辑或运算符)就像 == 运算符(相等运算符)一样,总是返回 1 或 0。所有其他逻辑运算符(logical operators)和比较运算符(comparison operators)也是如此。在这个情况下,由于至少有一个操作数(operands)是非零,所以表达式的值为 1。如果你习惯于逻辑或返回它看到的第一个真值的语言,这可能会令人不愉快,因为在那种情况下它会是 42。

  4. 表达式 -1 < 1 的值是什么?这看起来很明显:值是 1,因为比较为真。这个问题真的只是为了引出下一个。

  5. 表达式 -1 < 1U 的值是什么?这应该和之前一样… 但事实并非如此。-1 的类型是 int(整型),而 1U 的类型是 unsigned int(无符号整型)。为了进行比较,-1 被转换为无符号整型,然后将结果值与 1 进行比较。结果值大于 1,所以比较为假,表达式的结果为 0。

  6. 给定一个声明为 char a[10] 的局部变量,sizeof(a) 的值是多少?
    答案很简单:a 是一个包含 10 个 char 的数组,sizeof(a) 的值就是 10。你可能会倾向于回答 10 * sizeof(char),这在技术上是正确的,但却是多余的。根据定义,sizeof(char) 总是 1。尽管 C 标准提到了 “字节”(bytes),但它对这个词有自己独特的定义,这个定义对应于 char 类型。即使在一个 char 为 32 位的特殊系统上,sizeof(char) 仍然是 1。如果你想知道 char 在绝对意义上的大小,CHAR_BIT 宏会告诉你它包含多少比特。

  7. 给定一个声明为 void f(char a[10]) 的函数,在函数体内部 sizeof(a) 的值是多少?
    这和前一个问题相同,所以答案是 10,对吧?

你现在肯定已经明白不是那样了。

这是 C 语言中一个极其怪异的角落。声明为数组类型的函数参数会被隐式地转换为指针类型。这两个函数原型是完全相同的:

void f(char a[10]);
void f(char *a);

数组表示法(array notation)基本上只是一种让程序员为代码添加注释的方式。我们可以将它理解为 f 接受一个包含 10 个 char 的数组。但对编译器来说,它只是表示 f 接受一个指向 char 的指针(pointer)。

相应地,sizeof (a) 的值就是你系统上指针的大小。在 Apple 平台上,它将是 4 或 8,取决于你是在 32 位模式还是 64 位模式下运行。

  1. UINT_MAX + 1 的值是多少?在一种充满陷阱、一旦违反规则就会产生未定义行为的语言中,无符号类型(unsigned types)的行为规范得令人安心。所有计算结果都是按最大可表示数加一取模得出的。因此,UINT_MAX + 1 的结果就是零。

  2. INT_MAX + 1 的值是多少? 我们的喜悦是短暂的。你可能会认为结果也是零,或者会回绕到 INT_MIN,或者会固定为 INT_MAX。这些都有可能。这个值也可能是 42,或是一个随机生成的数字。执行这条语句甚至可能导致你的浏览器访问 zombo.com、重启你的计算机,或者清空 ~ 目录中的所有内容。有符号整数溢出是未定义行为(undefined behavior),这意味着此时编译器基本上可以为所欲为。

  3. NULL 的类型是什么? 它是一个指针类型,所以必然属于某种指针。也许是 void *

它可能是 void *。它也可能是 int 或其他整数类型!标准规定,NULL 是一个空指针常量(null pointer constant),而空指针常量是 “一个值为 0 的整型常量表达式,或该表达式被强制转换为 void * 类型后的表达式……”

正因为如此,当你将 NULL(或 nil)传递给可变参数函数(variadic function)时必须小心,因为除非你进行类型转换,否则它的类型并不明确。以下代码在技术上是不正确的:

printf("%p", NULL);

虽然现代编译器通常能很好地确保这一点是安全的。例如,Clang 使用编译器内置的 NULL,它在这里的行为是适当的。

  1. 对于类型 char(字符类型)、shortintlonglong longsizeof(大小运算符)是多少?char 的答案当然是 1。其余的答案是 “视情况而定”。

对于 C 实现(C implementation)来说,让 char 成为 64 位量,并且这个列表上每个数据类型的大小都为 1,是完全合法的。我不确定这是否曾经被实现过,但有一些环境,例如数字信号处理器(digital signal processors),其中 char 是 32 位量,并且从 charint 的所有类型的 sizeof 都是 1。

然而,虽然 “视情况而定” 的答案在技术上是正确的,但了解我们使用的系统的实际数量也很有用。对于 Apple 平台(Apple platforms),数字如下:

  • sizeof(char) == 1

  • sizeof(short) == 2

  • sizeof(int) == 4

  • sizeof(long) == 4 在 32 位代码中,和 8 在 64 位代码中

  • sizeof(long long) == 8

注意,当精确大小在你的代码中很重要时,你仍然应该使用 stdint.h(标准整数头文件)中的 uintXX_t 类型(uintXX_t types),而不是依赖上述数量。当 Apple 的下一个大平台突然对所有东西都有奇怪的尺寸时,你会庆幸你这么做了。

  1. 使用 printf 打印 int 的格式说明符是什么? 这里没有花招:是 %d%i,完全取决于你的偏好。

  2. 使用 printf 打印 short 的格式说明符是什么? 你需要使用 h 修饰符来表明参数是 short,例如 %hd。然而,这并非必须:你可以直接使用 int 的格式说明符。小于 int 的类型在作为可变参数传递给像 printf 这样的可变参数函数时,会被提升为 int。这意味着 %d 不仅适用于 int,也适用于 shortchar

(技术上讲,%dchar 可能无效。如果 char 是无符号的,并且 sizeof(int) == 1,那么 char 会被提升为 unsigned int 而不是 int。那么,如果 char 的值无法用有符号 int 表示,尝试将其当作有符号 int 处理就是无效的。这在你可能遇到的任何平台上都不太可能成为问题。)

  1. 使用 printf 打印 double 的格式说明符是什么? 另一个简单的:%f 是最常见的,printf 还支持 %F%e%E%g%G%a%A。请查阅文档了解所有这些变体的用途说明。

  2. 使用 printf 打印浮点数(float)时,应使用什么格式说明符(format specifier)? 这是一个关于参数提升(argument promotion)的陷阱问题。当用作可变参数(variable argument)时,float 会被提升为 double,因此所有用于 double 的格式说明符同样适用于 float

  3. 表达式 *(char *)NULL 的值是什么? 一个常见的回答是 “它会崩溃”,这确实是可能的结果之一。然而,这又是未定义行为(undefined behavior)的一个例子:你根本不被允许解引用 NULL,而当你尝试时,编译器可以执行任何操作。特别是,如果编译器能在编译时发现你试图解引用 NULL,它便可以假定该代码实际上永远不会执行(因为这是非法的),并据此优化掉相关分支。不要故意编写上述代码来导致崩溃;应改为调用 abort()

  4. 字符串字面量(string literal) "abcde" 的类型是什么? 字符串字面量是具有适当大小的 char 数组。具体的该字符串字面量类型为 char[6]。请注意多出的数组元素用于存放终止的 NUL 字符。

修改字符串字面量(string literal)的内容是非法的,但其类型并非 const char[6]。这是 C 语言尚未引入 const 关键字时代的历史遗留产物。你只需知道不能修改它们即可。

sizeof

  1. sizeof("abcde") 的值是多少? 根据前一个答案的提示,结果应当很明确:6。再次强调,数组大小包含了用于终止的 NUL(NUL 终止符)。人们容易数出字符串中的五个字符,从而误以为大小是 5。还需注意,虽然字符串字面量通常被当作 char * 处理,但它们实质上是数组而非指针,因此 sizeof 返回的是整个数组的大小,而非指针的大小。

free(NULL)

  1. 执行 free(NULL) 的结果是什么? 这是这批问题中我最喜欢的一个,因为尽管答案极其简单,但很多人并不知晓。我曾遇到许多堪称” C 语言专家” 的聪明人也会答错。

答案是:什么都不会发生。标准明确规定 free(NULL) 是一个空操作(no-op)。每当你在 free 调用前看到用于防护的 if 语句时,其实都可以将其移除:

if(ptr != NULL) // unnecessary!
free(ptr);

当然,如果你喜欢,为了清晰起见保留检查也没有什么问题。

几乎我交谈过的每个人都认为检查是必要的,并且 free (NULL) 会崩溃或引发未定义行为。我不确定这个想法从何而来,但它很引人入胜。

  1. 执行 realloc (NULL, sizeof (int)) 的结果是什么?realloc 在概念上是 malloc、memcpy 和 free 的组合。就像 free (NULL) 是空操作一样,realloc 作用于 NULL 只是分配一些新内存。上述完全等同于 malloc (sizeof (int))。当你有一个动态调整大小的缓冲区时,这可能很方便。你可以将指针初始化为 NULL,并使用同一个 realloc 进行初始分配和调整大小。

Conclusion

这就是今天的内容。我希望你比之前多了解一些关于 C 的知识。

Friday Q & A 由读者建议驱动。如果你有什么想在这里看到的内容,请发送过来!


#Original (English)

Source: https://www.mikeash.com/pyblog/friday-qa-2013-05-31-c-quiz.html

I thought I’d mix things up a bit today and give my readers a quiz. The C language is perhaps the most popular computer language in existence, but it’s also quite odd, and because of that often poorly understood. I’d like to give you a quiz to see how much you know about some of the odd but useful corners of the language.

QuestionsHere are the questions. The answers will follow. Try to answer all of the questions yourself before reading the answers. Try to do it from memory alone first, then check the answer using your compiler, the language spec, or whatever else you want to use. Keep in mind that answers found by testing your compiler may only reflect your environment, and will not necessarily be generally correct.

  • What is the type of the character literal ‘a’?

  • What is the type of the expression a == b?

  • What is the value of the expression 1 == 1? How about 0 == 1?

  • What is the value of the expression 42 || 0?

  • What is the value of the expression -1 < 1?

  • What is the value of the expression -1 < 1U?

  • Given a local variable declared as char a[10], what is the value of sizeof(a)?

  • Given a function declared as void f(char a[10]), what is the value of sizeof(a) within the function body?

  • What is the value of UINT_MAX + 1?

  • What is the value of INT_MAX + 1?

  • What is the type of NULL?

  • What is sizeof for types char, short, int, long, and long long?

  • What is the format specifier for printing an int using printf?

  • What is the format specifier for printing a short using printf?

  • What is the format specifier for printing a double using printf?

  • What is the format specifier for printing a float using printf?

  • What is the value of the expression *(char *)NULL?

  • What is the type of the string literal “abcde”?

  • What is the value of sizeof(“abcde”)?

  • What is the result of executing free(NULL)?

  • What is the result of executing realloc(NULL, sizeof(int))?

IntermissionLet’s give a little space for people whose eyes drifted down while reading the last few questions.

A little more space than that, I think.

Just another paragraph or two.

Did you try to figure out the answers yourself? You should give it a shot before you continue on.

Last chance! Answers follow.

  1. What is the type of the character literal ‘a’?This one’s easy. It’s a character literal, so its type is char. Right?

Nope. The type is int. I’m not entirely sure why, but that’s what the standard says. The value is still what you’d expect, so there’s little practical consequence to this. Generally, you’d only notice if you tried to use sizeof on one.

  1. What is the type of the expression a == b?The natural response for someone coming from a more sane language would be BOOL, or bool, or maybe even _Bool, which is C’s official built-in boolean type as of the C99 standard. However, this is not the case: comparison expressions always have type int.

  2. What is the value of the expression ‘1 == 1’? How about ‘0 == 1’?Although the type is always int, at least some sanity prevails: the value is always 1 when the comparison is true, and 0 when false. The answers here are, respectively, 1 and 0.

  3. What is the value of the expression 42 0?The operator is just like the == in that it always returns 1 or 0. The same is true of all other logical and comparison operators. In this case, since at least one of the operands is non-zero, the value of the expression is 1. This can be an unpleasant surprise if you’re used to languages where the logical or returns the first true value it sees, which in this case would be 42.

  4. What is the value of the expression -1 < 1?This is as obvious as it looks: the value is 1, because the comparison is true. This question is really only here to set up the next one.

  5. What is the value of the expression -1 < 1U?This should be the same as before… but it’s not. The type of -1 is int, while the type of 1U is unsigned int. In order to make the comparison, the -1 is converted to an unsigned int and the resulting value compared with 1. The resulting value is larger than 1, so the comparison is false, and the expression yields 0.

  6. Given a local variable declared as char a[10], what is the value of sizeof(a)?The answer is pretty simple: a is an array of 10 chars, and the value of sizeof(a) is 10. You might be tempted to answer 10 * sizeof(char), which is technically correct, but redundant. By definition, sizeof(char) is always 1. Although the C standard talks about “bytes”, it has its own peculiar definition of the word, which corresponds to the char type. Even on an exotic system where char is 32 bits, sizeof(char) is still 1. If you want to know how big a char is in absolute terms, the CHAR_BIT macro will tell you how many bits it contains.

  7. Given a function declared as void f(char a[10]), what is the value of sizeof(a) within the function body?This is the same as the previous question, so the answer is 10, right?

You surely know better than that by now.

This is a seriously weird corner of C. Function parameters declared as array types are invisibly converted to pointer types. These two function prototypes are exactly the same:

void f(char a[10]);
void f(char *a);

The array notation is basically just a way for the programmer to document the code a bit. We can read this as saying that f takes an array of 10 chars. To the compiler, it just says that f takes a pointer to char.

Accordingly, sizeof(a) is whatever the size of a pointer is on your system. On Apple platforms, it’ll be 4 or 8 depending on whether you’re running in 32-bit mode or 64-bit mode.

  1. What is the value of UINT_MAX + 1?In a language full of pitfalls and undefined behavior the moment you step outside the rules, the behavior of unsigned types is comfortingly well-specified. All results are computed modulo the largest representable number plus one. Thus, UINT_MAX + 1 simply produces zero.

  2. What is the value of INT_MAX + 1?Our joy is short-lived. You might think this would also be zero, or that it would wrap around to INT_MIN, or that it would clamp to INT_MAX. All of these are possible. The value can also be 42, or a randomly generated number. Or executing the statement could cause your web browser to visit zombo.com, reboot your computer, or erase everything in ~. Signed integer overflow is undefined behavior, which means the compiler is allowed to do basically whatever it wants at this point.

  3. What is the type of NULL?It’s a pointer type, so it must be some kind of pointer. Maybe void *?

It can be void *. It can also be int or another integer type! The standard says that NULL is a null pointer constant, which in turn is “An integer constant expression with the value 0, or such an expression cast to type void *…”

Because of this, you have to be careful when passing NULL (or nil) into a variadic function, since the type isn’t well defined unless you cast it. This is technically incorrect:

printf("%p", NULL);

Although modern compilers are generally good about ensuring that this is safe. Clang, for example, uses a compiler built-in for NULL which behaves appropriately here.

  1. What is sizeof for types char, short, int, long, and long long?The answer for char is, of course, 1. The answers for the rest are, “it depends”.

It would be perfectly legal for a C implementation to make char be a 64-bit quantity, and have the size of every data type on this list be 1. I’m not sure if this has ever been done, but there are some environments, such as digital signal processors, where char is a 32-bit quantity, and sizeof everything up to int is 1.

However, while the “it depends” answer is technically correct, it’s also useful to know the actual quantities for the systems we use. For Apple platforms, the numbers are:

  • sizeof(char) == 1

  • sizeof(short) == 2

  • sizeof(int) == 4

  • sizeof(long) == 4 in 32-bit code, and 8 in 64-bit code

  • sizeof(long long) == 8

Note that you should still use the uintXX_t types from stdint.h when precise sizes are important in your code, rather than relying on the above quantities. You’ll be glad you did when Apple’s next big platform suddenly has weird sizes for everything.

  1. What is the format specifier for printing an int using printf?There’s no trickery here: it’s either %d or %i, depending entirely on your preference.

  2. What is the format specifier for printing a short using printf?You use an h modifier to indicate that the argument is a short, e.g. %hd. However, this is unnecessary: you can simply use the format specifier for int. Types smaller than int are promoted to int when passed as a variable argument to a variadic function like printf. That means that %d works not only for int, but also short and char.

(Technically, %d may not be valid for char. If char is unsigned, and if sizeof(int) == 1, then a char will be promoted to an unsigned int instead of an int. Then, if the value of the char can’t be represented in a signed int, attempting to treat it as one is invalid. This is unlikely to be a concern on any platform you encounter.)

  1. What is the format specifier for printing a double using printf?Another easy one: %f is the most common one, and printf also supports %F, %e, %E, %g, %G, %a, and %A. See the documentation for an explanation of what all these variants are for.

  2. What is the format specifier for printing a float using printf?This is another trick question due to argument promotion. When used as a variable argument, float is promoted to double, so all of the format specifiers for double also work for float.

  3. What is the value of the expression *(char *)NULL?A common response to this would be “it crashes”, which is certainly one possible outcome. However, this is yet another instance of undefined behavior: you’re simply not allowed to dereference NULL, and when you try, the compiler is allowed to do anything. In particular, if the compiler can figure out at compile time that you’re trying to dereference NULL, it’s free to do things like assume that code can never actually execute (because it would be illegal) and optimize out branches accordingly. Don’t write the above to intentionally cause a crash; call abort() instead.

  4. What is the type of the string literal “abcde”?String literals are char arrays with the appropriate size. The specific string literal has type char[6]. Note the extra array element for the terminating NUL character.

It is illegal to modify the contents of a string literal, but the type is not const char[6]. This is a holdover from the days when C had no const keyword. You’re just supposed to know that you can’t modify them.

  1. What is the value of sizeof(“abcde”)?This should be obvious given the previous answer: 6. Again, note the extra element for the terminating NUL. It’s easy to count five characters in the string and mistakenly think that the size is 5. Also note that, while string literals are usually treated as char *, they are arrays, not pointers, and so sizeof returns the size of the array, not the size of a pointer.

  2. What is the result of executing free(NULL)?This is my favorite question of the bunch, because it’s so poorly known despite being so simple. I’ve encountered many smart people who I’d have no qualms describing as “C experts” who get this wrong.

The answer is: nothing. free(NULL) is defined as a no-op by the standard. Every time you see an if guard on a free call, it can be removed:

if(ptr != NULL) // unnecessary!
free(ptr);

Of course, there’s nothing wrong with leaving the check for clarity if you prefer.

Virtually everyone I’ve talked to about this thought that the check is necessary and free(NULL) would crash or otherwise invoke undefined behavior. I’m not sure where that idea came from, but it’s fascinating.

  1. What is the result of executing realloc(NULL, sizeof(int))?realloc is, conceptually, a malloc, memcpy, and a free. Much like free(NULL) is a no-op, realloc on NULL just allocates some fresh memory. The above is exactly equivalent to malloc(sizeof(int)). This can be convenient when you have a dynamically-resized buffer. You can start the pointer out as NULL and use the same realloc for the initial allocation as for resizes.

ConclusionThat’s it for today. I hope you know a little more about C than you did before.

Friday Q&A is driven by reader suggestions. If you have something you’d like to see covered here, send it in!