C 语言类型限定符(上)

Mike Ash Friday Q&A 中文译文:C 语言类型限定符(上)

作者 TommyWu
封面圖片: C 语言类型限定符(上)

译文 · 原文: Friday Q&A 2009-06-26: Type Qualifiers in C, Part 1 · 作者 Mike Ash

原文:https://www.mikeash.com/pyblog/friday-qa-2009-06-26-type-qualifiers-in-c-part-1.html 发布:2009-06-26 作者:Mike Ash 译者:MiMo(mimo-v2.5-pro);代码块保留英文原样


欢迎回来,又一期温暖而可爱的 Friday Q & A。本周,我将讨论 C 语言中类型限定符(type qualifiers)的使用,这个主题由 Nate Vander Wilt 提出。

它们是什么 首先,需要谈谈类型限定符实际上是什么。简而言之,它们是一种可用于修改任意类型属性的关键字。最常见的是 const。类型限定符不应与那些修改特定变量存储方式的关键字混淆,后者被称为存储说明符(storage specifiers)。

让我用一个例子来说明:

static const int *x;

这里 static 是一个存储说明符(storage specifier),它改变了变量 x 的实际存储方式。const 关键字则是一个类型限定符(type qualifier),它用来限定 int 类型。

例如,const 用在 typedef 中是完全合理的,如下所示:

typedef const int MyConstInt;
typedef static int MyStaticInt; // will not compile, does not make sense!
  • const

  • restrict

  • volatile

本周我将讨论 const 和 restrict,并于下周结束对 volatile 的讨论。

const 关键字

const 限定符无疑是这三者中最常用、最有用的。其含义非常容易理解:当应用于某个类型时,const 会使该类型变为只读。

const 可以在多种场景下发挥作用:

  • 用于函数指针参数。一个 const 函数指针参数意味着该函数不会修改指针所指向的值。例如,查看标准库中的 strchr 函数。其第一个参数被声明为 const char * 类型,因为该函数仅读取字符串,而不修改它。在您自己的代码中,当接收不会被修改的指针参数时,采用这种做法也是有益的。

  • 用于函数返回值。在此场景下,const 表示返回的数据是只读的,调用方不允许修改它。例如,Cocoa 中的 -[NSString UTF8String] 方法返回 const char *。这意味着你可以使用这些数据,但不能向其写入。该数据可能是指向某种内部存储或缓存的指针,而 const 用于强制执行访问控制,以实现此用途。

  • 用于局部或全局指针变量。在此场景下,const 表示该指针不能被用来修改其指向的内容。当你确定你的使用方式是只读的时候,这有助于保持正确性;但更常见的用途是,在访问那些返回 const 指针的函数(如上一点所述)所返回的 const 返回值时,用来让编译器不再发出警告。

  • 用于局部或全局非指针变量。这便于声明编译时常量。例如,const int kMeaning = 42; 声明了一个常量。此处的 const 将有助于防止你意外地更改此值,并且也可能允许编译器进行一些否则无法进行的优化。

由于这是一个侧重于 Mac 技术的博客,我也想简要讨论一下 const 在 Objective-C 中的应用。特别需要强调的是,你绝不应该将 Objective-C 对象类型声明为 const。例如,下面这种做法并不是确保 NSString 不可变性的正确方式:

const NSString *immutableNSStringPointer;

更具体地说,如果你这样声明一个变量,然后尝试在任何地方使用它,你将收到大量关于违反变量常量性(constness)的无用警告。

如果你想要一个常量 NSString 指针呢?不是指向一个常量 NSString 的指针,而是一个本身不可更改的常量指针。这就相当于上面 const int kMeaning = 42; 例子的 NSString 版本。这很容易做到,你只需通过调整 const 的位置,直接将其应用于变量本身:

NSString * const kConstantStringPointer = @"hello, world";

那么,restrict 究竟意味着什么呢?其实很简单:当一个指针被声明为 restrict 时,它告诉编译器,在该作用域内,这是唯一将访问特定内存块的指针。

但这又是什么意思呢?考虑以下代码:

char *src, *dst;
...
for(int i = 0; i < len; i++)
dst[i] = src[i];

这就是 restrict 关键字发挥作用的地方。通过将 srcdst 声明为 restrict,你是在向编译器保证:你亲自确保它们不会指向同一块内存,因此编译器可以放心地为这个循环生成更优、更快的代码。

实际上,你很少会在自己的代码中使用 restrict。然而,你可能会在其他地方遇到它。例如,以下是 memcpy 函数的原型:

void *memcpy(void *restrict s1, const void *restrict s2, size_t n);

总结
以上便是第 1 部分的全部内容。现在你已明白 constrestrict 的含义及用法。const 非常实用,每位 C 程序员都应掌握其使用方法。restrict 关键字除非用于编写某些高度优化的代码,否则基本无用,但了解其基础仍有必要。

下周我将讨论 volatile 关键字。它在 constrestrict 之间处于一个有趣的中间地带:远不及前者实用和常见,但比后者应用更广泛且略具实用性。它也常被误解和误用。关于它的作用、使用方式以及(最重要的)错误用法,所有细节都将在下周揭晓。

一如既往,周五问答(Friday Q & A)的内容由你的想法驱动,欢迎提交!若有希望在此讨论的主题,请在评论区留言或直接发邮件告诉我。


#Original (English)

Source: https://www.mikeash.com/pyblog/friday-qa-2009-06-26-type-qualifiers-in-c-part-1.html

Welcome back to another warm and fuzzy edition of Friday Q&A. This week I’m going to discuss the use of type qualifiers in C, a subject suggested by Nate Vander Wilt.

What They Are The first thing is to talk about what type qualifiers actually are. In short, they’re a keyword which can be used to modify the properties of an arbitrary type. The most common one is const. Type qualifiers should not be confused with keywords which modify the storage of a particular variable. Those are called storage specifiers.

Let me illustrate with an example:

static const int *x;

Here, static is a storage specifier. It modifies the variable x to change how x is actually stored. The const keyword is a type qualifier, which modifies the int type.

By way of illustration, it makes perfect sense to use const in a typedef, like so:

typedef const int MyConstInt;
typedef static int MyStaticInt; // will not compile, does not make sense!
  • const

  • restrict

  • volatile

This week I will discuss const and restrict, and I’ll finish up next week with a discussion of volatile.

The const keyword The const qualifier is far and away the most common and the most useful of the three. Its meaning is very easy to understand: when applied to a type, const makes that type become read-only.

There are several places where const can be useful:

  • On a function pointer parameter. A const function pointer parameter means that the function won’t modify the value that the pointer points to. For example, look at the standard strchr function. The first parameter is declared of type const char *, because this function only reads the string, and doesn’t modify it. This is a useful thing to do with your own code as well when taking pointer parameters that won’t be modified.

  • On a function pointer return value. Used here, const indicates that the data being returned is read-only and the caller is not allowed to modify it. For example, the -[NSString UTF8String] method in Cocoa returns a const char *. This means you can use the data but you’re not allowed to write to it. The data may be a pointer to some kind of internal storage or cache, and the const is used to enforce access to make this useful.

  • On a local or global pointer variable. When used here, const indicates that this pointer can’t be used to modify its contents. This can be useful to preserve correctness when you know that your use is read-only, but is most often useful to shut up the compiler when accessing const return values from functions that return const pointers as in #2.

  • On a local or global non-pointer variable. This is handy to declare a compile-time constant. For example, const int kMeaning = 42; declares a constant. The const here will help prevent you from changing this value by accident, and may also allow the compiler to do some optimizations that otherwise would not be possible.

Since this is a heavily Mac-centric blog, I also want to briefly discuss const as it applies to Objective-C. In particular, you should never declare an Objective-C object type as const. For example, this is not a good way to enforce the immutability of an NSString:

const NSString *immutableNSStringPointer;

More concretely, if you declare a variable like this and then try to use it anywhere, you’ll get a huge number of useless warnings about violating the constness of your variable.

What if you want a constant NSString pointer? Not a pointer to a constant NSString, but a constant pointer, one which can’t be changed. The NSString equivalent of the const int kMeaning = 42; example from above. This can easily be done, you just have to apply the const to the variable directly, by altering its position:

NSString * const kConstantStringPointer = @"hello, world";

So what does restrict mean, exactly? It’s actually pretty simple: when a pointer is declared with restrict, it tells the compiler that this is the only pointer which will be accessing a particular chunk of memory in that scope.

But what does that mean? Consider the following code:

char *src, *dst;
...
for(int i = 0; i < len; i++)
dst[i] = src[i];

This is where the restrict keyword comes in. By declaring src and dst as restrict, you tell the compiler that you are personally guaranteeing that they won’t point into the same block of memory, and thus that the compiler should feel free to generate nice, fast code for this loop.

It’s unlikely that you’ll have occasion to use restrict in your own code. However you may encounter it elsewhere. For example, here is the prototype for the memcpy function:

void *memcpy(void *restrict s1, const void *restrict s2, size_t n);

Wrapping Up That brings us to the end of Part 1. Now you know what const and restrict mean and how to use them. The const can be very useful and every C programmer should know how to use it. The restrict keyword is mostly useless unless you’re writing certain kinds of highly optimized code, but it’s still good to know the basics.

Next week I’ll discuss the volatile keyword. It sits in an interesting middle ground in between const and restrict: not nearly as useful or common as the former, but much more widely used and marginally more useful than the latter. It’s also very frequently misunderstood and misused. For all the details on what it does, how to use it, and (most importantly) how not to use it, check back next week.

And as always, Friday Q&A runs on your ideas, so send them in! If you have a subject that you would like to see discussed here, post it in the comments or e-mail it to me directly.