C中const限定变量的一些实际用途是什么?

时间:2010-08-08 12:49:38

标签: c const

正如最近几个问题中所讨论的那样,在C中声明const - 限定变量(与C ++中的const变量相对,或者在C中指向const)通常非常有用目的不大。最重要的是,它们不能用于常量表达式

话虽如此,C中const限定变量的合法用途是什么?我可以想到最近在我使用的代码中出现的一些,但肯定必须有其他代码。这是我的清单:

  • 使用它们的地址作为指针的特殊sentinel值,以便永远不会比较任何其他指针。例如:char *sentinel(void) { static const char s; return &s; }或简称const char sentinel[1];因为我们只关心地址,如果写入对象实际上无关紧要,const的唯一好处就是编译器通常会将其存储在由mmap可执行文件或零页副本支持的只读内存中。

  • 使用const限定变量从库(尤其是共享库)导出值时,值可能会随库的新版本而变化。在这种情况下,在库的接口头中简单地使用#define将不是一个好方法,因为它会使应用程序依赖于它构建的库的特定版本中的常量值。

  • 与之前的使用密切相关,有时您希望将预定义的对象从库中暴露给应用程序(典型示例为stdinstdout和{{1}来自标准库)。使用该示例,由于大多数系统实现共享库的方式,stderr将是一个非常糟糕的实现 - 通常它们需要将整个对象(此处为extern FILE __stdin; #define stdin (&__stdin))复制到应用程序确定的地址是链接的,并引入对象大小的依赖(如果重建库并且对象的大小发生变化,程序将中断)。使用FILE指针(不指针指向const)可修复所有问题:const,其中extern FILE *const stdin;指针初始化为指向预定义对象(它本身很可能被宣布为const)在图书馆内部的某个地方。

  • 数学函数,字符属性等的查找表。这是我原本忘记包含的一个显而易见的表,可能是因为我在考虑算术/指针类型的单个static变量,因为那是问题主题首先出现了。感谢Aidan引发我的记忆。

  • 作为查找表的变体,实现状态机。艾丹提供了一个详细的例子作为答案。我发现相同的概念在没有任何函数指针的情况下通常也非常有用,如果你可以根据几个数字参数对每个状态的行为/转换进行编码。

其他人对const - C中的合格变量有一些聪明的实际用途吗?

6 个答案:

答案 0 :(得分:6)

const经常用于嵌入式编程,用于映射到微控制器的GPIO引脚。例如:

typedef unsigned char const volatile * const tInPort;
typedef unsigned char                * const tOutPort;

tInPort  my_input  = (tInPort)0x00FA;
tOutPort my_output = (tOutPort)0x00FC;

这两个都阻止程序员意外地改变指针本身,这在某些情况下可能是灾难性的。 tInPort声明还会阻止程序员更改输入值。

使用volatile可防止编译器假设my_input的最新值将存在于缓存中。因此,my_input的任何读取都将直接进入总线,因此始终从设备的IO引脚读取。

答案 1 :(得分:2)

例如:

void memset_type_thing(char *first, char *const last, const char value) {
    while (first != last) *(first++) = value;
}

last不能成为常量表达式的一部分的事实既不在这里,也不在那里。 const是类型系统的一部分,用于表示其值不会更改的变量。我没有理由在我的函数中修改last的值,因此我将其声明为const

我无法理解它const,但后来我根本无法使用静态类型的语言; - )

答案 2 :(得分:2)

PC-lint warning 429遵循预期应该使用指向已分配对象的本地指针

  • 将其复制到另一个指针或
  • 将其传递给“脏”函数(这应该剥离指针的“保管”属性)或
  • 释放它或
  • 通过return语句或pass-by-pointer参数将其传递给调用者。

“脏”是指一个函数,其对应的指针参数具有非const基类型。警告的描述从“脏”标签中解析了库函数,例如strcpy(),显然是因为这些库函数都没有获得指向对象的所有权。

因此,当使用PC-lint等静态分析工具时,被调用函数的const限定符会保留本地分配的内存区域。

答案 3 :(得分:1)

  • 当类型不是具有可用文字的类型(即除数字之外的任何其他类型)时,const变量很有用。对于指针,您已经提供了一个示例(stdin和co),您可以使用#define,但是您可以获得一个可以轻松分配的左值。另一个示例是structunion类型,其中没有可分配的文字(仅初始值设定项)。例如,考虑一个合理的C89复数的实现:

    typedef struct {double Re; double Im;} Complex;
    const Complex Complex_0 = {0, 0};
    const Complex Complex_I = {0, 1}; /* etc. */
    
  • 有时您只需要一个存储对象而不是文字,因为您需要将数据传递给期望void*size_t的多态函数。以下是cryptoki API(a.k.a. PKCS#11)中的一个示例:许多函数需要一个作为CK_ATTRIBUTE数组传递的参数列表,基本上定义为

    typedef struct {
        CK_ATTRIBUTE_TYPE type;
        void *pValue;
        unsigned long ulValueLen;
    } CK_ATTRIBUTE;
    typedef unsigned char CK_BBOOL;
    

    所以在您的应用程序中,对于布尔值属性,您需要将指针传递给包含0或1的字节:

    CK_BBOOL ck_false = 0;
    CK_ATTRIBUTE template[] = {
        {CKA_PRIVATE, &ck_false, sizeof(ck_false)},
    ... };
    

答案 4 :(得分:1)

const对于我们使用数据以特定方式引导代码的某些情况非常有用。例如,这是我在编写状态机时使用的模式:

typedef enum { STATE1, STATE2, STATE3 } FsmState;
struct {
    FsmState State;
    int (*Callback)(void *Arg);
} const FsmCallbacks[] = {
    { STATE1, State1Callback },
    { STATE2, State2Callback },
    { STATE3, State3Callback }
};

int dispatch(FsmState State, void *Arg) {
    int Index;
    for(Index = 0; Index < sizeof(FsmCallbacks)/sizeof(FsmCallbacks[0]); Index++)
        if(FsmCallbacks[Index].State == State)
            return (*FsmCallbacks[Index].Callback)(Arg);
}

这类似于:

int dispatch(FsmState State, void *Arg) {
    switch(State) {
        case STATE1:
            return State1Callback(Arg);
        case STATE2:
            return State2Callback(Arg);
        case STATE3:
            return State3Callback(Arg);
    }
}

但我更容易维护,特别是在与状态相关的更复杂行为的情况下。例如,如果我们想要一个特定于状态的中止机制,我们将结构定义更改为:

struct {
    FsmState State;
    int (*Callback)(void *Arg);
    void (*Abort)(void *Arg);
} const FsmCallbacks[] = {...};

我不需要为新状态修改abortdispatch例程。我使用const来阻止表在运行时更改。

答案 5 :(得分:0)

它们可用于无法通过用户代码更改的内存映射外设或寄存器,只能用于微处理器的某些内部机制。例如。在PIC32MX上,某些指示程序状态的寄存器是合格的const volatile - 因此您可以读取它们,并且编译器不会尝试优化,例如重复访问,但您的代码无法写入它们。

(我手边没有任何代码,所以我现在不能举一个很好的例子。)