张银峰的编程课堂

使用const保护对象不被改写

关键字const可以限定指定类型的对象为只读属性(即不可写),这可以防止对象在不必要的情况下被改写。比如对于一个打印名称的函数,如果没有const限定参数,那么我们就不能阻止函数的实现者去改写其内容。

void print_name(char *name)
{
    if (name != NULL)
    {
        name[0] = 'X';
        printf("%s", name);
    }
}

当使用const修饰name后,代码中的赋值语句在编译阶段就会被阻止,由此保证了数据的安全性。

void print_name(const char *name)
{
}

需要强调的是,在C语言中const限定对象是只读的,而不是表示对象是常量。因此它们不能用于数组定义、case语句等需要常量表达式的地方。

void foo()
{
    const int size = 3; // 只读量而非常量
    int arr[size];      // 错误,size不是常量

    switch (1)
    {
    case size:          // 错误:case 表达式不是常量
        break;
    }
}

在需要常量的地方,枚举常量与#define在这点上可以帮助我们。

void foo()
{
    #define MAXSIZE 4
    enum { BUFSIZE = 5 };

    int cont[MAXSIZE];  // 正确
    char buf[BUFSIZE];  // 正确

    switch (1)
    {
    case MAXSIZE:   // 正确
    case BUFSIZE:   // 正确
        break;
    }
}

由于语言标准自身的不完备,我们还是可以通过指针间接的改变只读量的值。

#include <stdio.h>

int main()
{
    const int size = 3; // 在C语言下size是只读量
    int *psize = &size;
    *psize = 5;
    printf("[C] size: %d\n", size);
    return 0;
}

glimix.com

从语言层面讲,表达式&size取得的是一个const int*,我们不能将其赋予一个int*,这里在没有使用强制类型转换的前提下由编译器通过(编译器遵循语言规范实现,大多数现代编译器会对此行代码给出警告),然后通过这个int*间接了修改了只读变量的值。同样的代码在C++下,编译器会阻止这个指针赋值操作,除非使用强制类型转换,但这也改变不了size是个常量的事实。

#include <stdio.h>

int main()
{
    const int size = 3;         // 在C++下size是常量
    int arr[size] = {0, 1, 2};  // 正确

    int *psize = (int*)&size;
    *psize = 5;
    printf("[C++] size: %d\n", size);

    return 0;
}

glimix.com

关键字const还可以应用于用户定义的类型上,如struct 、enum等,此时这些类型的对象将具有只读性质,改变其成员或值是不可行的。作为一条编码建议,推荐尽量使用const,它可以帮助我们尽早发现问题,提高程序的安全性。

#include <stdio.h>

int main()
{
    struct Data
    {
        unsigned key;
        unsigned hash;
    };

    enum MyEnum
    {
        E1, E2, E3
    };

    const struct Data dat = { 0, 0 };
    const float arr[3] = { 3, 2, 1 };
    const enum MyEnum e = E3;

    // 下面所有的赋值语句都是错误的
    // 被赋值的对象是不可修改的(只读性质)
    e = E2;
    arr[0] = 0;
    dat.key = 1;

    return 0;
}

glimix.com