正如最近几个问题中所讨论的那样,在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
将不是一个好方法,因为它会使应用程序依赖于它构建的库的特定版本中的常量值。
与之前的使用密切相关,有时您希望将预定义的对象从库中暴露给应用程序(典型示例为stdin
,stdout
和{{1}来自标准库)。使用该示例,由于大多数系统实现共享库的方式,stderr
将是一个非常糟糕的实现 - 通常它们需要将整个对象(此处为extern FILE __stdin; #define stdin (&__stdin)
)复制到应用程序确定的地址是链接的,并引入对象大小的依赖(如果重建库并且对象的大小发生变化,程序将中断)。使用FILE
指针(不指针指向const
)可修复所有问题:const
,其中extern FILE *const stdin;
指针初始化为指向预定义对象(它本身很可能被宣布为const
)在图书馆内部的某个地方。
数学函数,字符属性等的查找表。这是我原本忘记包含的一个显而易见的表,可能是因为我在考虑算术/指针类型的单个static
变量,因为那是问题主题首先出现了。感谢Aidan引发我的记忆。
作为查找表的变体,实现状态机。艾丹提供了一个详细的例子作为答案。我发现相同的概念在没有任何函数指针的情况下通常也非常有用,如果你可以根据几个数字参数对每个状态的行为/转换进行编码。
其他人对const
- C中的合格变量有一些聪明的实际用途吗?
答案 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遵循预期应该使用指向已分配对象的本地指针
“脏”是指一个函数,其对应的指针参数具有非const基类型。警告的描述从“脏”标签中解析了库函数,例如strcpy(),显然是因为这些库函数都没有获得指向对象的所有权。
因此,当使用PC-lint等静态分析工具时,被调用函数的const限定符会保留本地分配的内存区域。
答案 3 :(得分:1)
当类型不是具有可用文字的类型(即除数字之外的任何其他类型)时,const
变量很有用。对于指针,您已经提供了一个示例(stdin
和co),您可以使用#define
,但是您可以获得一个可以轻松分配的左值。另一个示例是struct
和union
类型,其中没有可分配的文字(仅初始值设定项)。例如,考虑一个合理的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[] = {...};
我不需要为新状态修改abort
和dispatch
例程。我使用const
来阻止表在运行时更改。
答案 5 :(得分:0)
它们可用于无法通过用户代码更改的内存映射外设或寄存器,只能用于微处理器的某些内部机制。例如。在PIC32MX上,某些指示程序状态的寄存器是合格的const volatile
- 因此您可以读取它们,并且编译器不会尝试优化,例如重复访问,但您的代码无法写入它们。
(我手边没有任何代码,所以我现在不能举一个很好的例子。)