C语言中const到底是什么意思?

来源:微信编程作者:小黄人头衔:程序员
导读:本期聚焦于小黄人创作的《C语言中const到底是什么意思?》,敬请观看详情。把变量声明成 const 之后,编译器会把它当作只读对象处理,任何试图修改该对象的赋值操作都会在编译阶段被拦截。这个特性经常被误解为定义常量,但在 C 语言里 const 的真实含义是只读限定,而不是编译期常量。理解这一点对分析指针、函数参数和内存布局都很关键。const 可以放在类型名前后,修饰变量、指针、数组和结构体成员。最需要厘清的是 const 与指针组合时,const 位置不同会产生不同约束,比如指向常量的指针和常量指针。通过 const 还能让函数接口更清晰,防止函数内部意外修改调用者传入的数据。本文将结合变量声明、指针组合、函数形参与实际编译行为,具体说明 const 到底代表什么、有哪些用法以及常见误区。

在 C 语言里,const 是一个类型限定符,用来告诉编译器某个对象在初始化之后不能被修改。它并不是用来定义传统意义上的常量的宏,而是一种只读约束。比如 const int max = 100; 声明了一个只读变量 max,程序运行期间不能再通过 max 这个名字进行赋值。任何试图修改 const 对象的表达式都会在编译阶段报错,但这种保护主要发生在编译期,并不代表数据一定存放在只读内存区域。

C语言中const到底是什么意思?

如果把 const 和宏常量混为一谈,就很容易在数组大小、条件编译、链接属性等场景下产生误解。要准确理解它,需要从声明语法、指针组合、函数接口以及底层存储几个角度分别分析。

一、const 的核心语义:只读变量而非编译期常量

const 修饰变量后,变量的值仍然需要在声明时或初始化时确定,但之后就不能再通过这个变量名赋值。这种只读约束是编译期检查,编译器看到向 const 变量赋值的代码会直接报错。比如下面的声明,注释掉的赋值语句会被编译器拒绝。

#include <stdio.h>

int main(void) {
    const int max = 100;
    /* max = 200; 编译错误:不能给只读变量赋值 */
    printf("max = %d, address = %p\n", max, (void *)&max);
    return 0;
}

需要注意,C 语言中的 const 变量并不是编译期常量。它有自己的存储空间,可以取地址,也可以通过指针间接访问。这与 C++ 中的 const 对象有细微差异:在 C++ 中某些 const 对象可以参与编译期常量表达式,但 C 语言通常不把 const int 当作整数常量表达式使用。因此像 case 标签、全局数组长度等需要编译期常量的位置,不能直接使用 const 变量,这时需要用 enum 或 #define 代替。

由于 const 变量有地址,所以可以通过强制类型转换去掉 const 限定,再通过指针修改。不过 C 标准规定修改一个声明为 const 的对象属于未定义行为,实际程序中不应依赖这种做法。下面的例子只作为说明,不应在实际代码里出现。

#include <stdio.h>

int main(void) {
    const int val = 10;
    int *p = (int *)&val; /* 强制去掉 const 限定,不建议 */
    /* *p = 20;           可能触发未定义行为,不要这样写 */
    printf("val = %d\n", val);
    return 0;
}

二、const 与指针的组合:分清指向常量和常量指针

const 与指针组合时最容易混淆,因为 const 放的位置不同,限制的对象也不同。常见的有三种写法:const int *p 表示指针 p 可以改变,但它指向的整数不能通过 p 修改;int * const p 表示指针 p 本身不能改变,但指向的整数可以修改;const int * const p 表示指针和指向内容都不可修改。

#include <stdio.h>

int main(void) {
    int a = 10;
    int b = 20;

    const int *p1 = &a;      /* 指向只读 int 的指针 */
    /* *p1 = 30;            错误:不能修改所指内容 */
    p1 = &b;                 /* 允许:指针本身可变 */

    int *const p2 = &a;      /* 只读指针,指向 int */
    *p2 = 40;                 /* 允许:所指内容可变 */
    /* p2 = &b;             错误:不能修改指针本身 */

    const int *const p3 = &a; /* 指针和所指内容都只读 */
    /* *p3 = 50;             错误 */
    /* p3 = &b;             错误 */

    printf("%d %d %d\n", *p1, *p2, *p3);
    return 0;
}

阅读这类声明时,可以从变量名开始向两侧读。比如 int * const p 从 p 开始,先看到 const,说明 p 本身是常量,再看到 *,说明它是一个常量指针,指向 int。而 const int *p 从 p 开始,先看到 *,说明 p 是指针,再看到 const int,说明指向的是只读整数。这种读法能帮助快速判断哪些部分可以变。

实际开发中,函数参数常用 const int * 或 const char *,目的就是让函数内部不能修改调用者传入的数据,但指针本身可以改变。如果希望指针变量本身的指向也不能改,可以在形参中写成 int * const p,不过这种用法相对少一些。

三、const 在函数接口与结构体中的应用

把 const 用在函数参数上,是提升接口安全性的常见做法。比如标准库字符串函数 strlen 的原型可以理解为 size_t strlen(const char *s),它通过 const 明确表示函数不会修改字符串内容。调用方看到 const 参数,也能知道传入的数据不会被改写,这对于使用只读缓冲区或多线程共享数据尤其重要。

当指针参数既不想修改数据,也不想让函数保存该指针的副本时,可以使用 const 配合指针本身,不过通常 const char * 已经足够表明意图。更复杂的安全约束可以通过 const 多层指针来表达,例如 const int **pp 表示二级指针最终指向只读整数。

结构体成员也可以声明为 const,但它会带来一个约束:结构体变量在创建时必须给该成员提供初始值,之后整个结构体都不能再通过赋值修改这个成员。如果结构体变量本身不是 const,其他非 const 成员仍然可以修改。下面的例子展示了一个带 const 成员的结构体和只读指针参数。

#include <stdio.h>

struct Device {
    const int id;
    char name[20];
};

void show(const struct Device *dev) {
    /* dev->id = 999;      错误:成员是只读 */
    /* dev->name[0] = 'X'; 错误:通过 const 指针不能修改 */
    printf("id=%d, name=%s\n", dev->id, dev->name);
}

int main(void) {
    struct Device d = {101, "sensor"};
    show(&d);
    return 0;
}

需要注意,如果结构体包含 const 成员,编译器通常不会为结构体生成默认的赋值操作,因为赋值操作需要修改该成员,这与只读约束矛盾。所以这种结构体更适合在创建后保持固定标识信息,比如设备 ID、版本号等。使用只读成员可以让标识信息在对象的生命周期内保持不变。

四、常见误区与实用建议

第一种常见误区是把 const 当成 #define 的替代品。宏在预处理阶段进行文本替换,没有类型,也没有作用域;而 const 变量有明确的类型、作用域和存储期,编译器可以做类型检查。正因如此,如果只是需要一个简单的编译期常量,enum 或 #define 更合适;如果需要一个带类型的只读变量,const 更合适。

第二种误区是认为用 const 声明的全局变量一定放在只读内存。在大多数桌面系统中,const 全局变量可能被放入只读数据段,但标准并不强制。如果代码使用强制类型转换修改这类对象,可能触发段错误,也可能悄悄修改成功,这取决于平台和优化级别。因此,从可移植性角度出发,不要试图绕过 const。

还有一种情况是 const 与 volatile 组合使用。嵌入式开发中常见 volatile const int *status_reg,它表示程序不能通过这个指针修改寄存器值,但寄存器的值可能被硬件改变,所以每次访问都要从内存重新读取。这说明 const 限制的是代码的写权限,而 volatile 控制的是读取优化,两者并不冲突。

#include <stdio.h>

#define MAX 100

enum { SIZE = 100 };

int main(void) {
    const int limit = 100;
    int a[MAX];
    int b[SIZE];
    /* int c[limit]; 在需要编译期常量的场合不要使用 const 变量 */

    printf("%zu %zu\n", sizeof(a), sizeof(b));
    return 0;
}

综合来看,使用 const 的最大价值不在于提高运行速度,而在于让接口意图更清晰、减少意外修改。凡是函数参数指向的数据不需要在函数内改变,就应当加上 const;凡是局部变量初始化后就不再变化,也可以用 const 标记。这样代码在编译阶段就能暴露更多错误,长期维护成本也会降低。

C语言const关键字只读变量修改时间:2026-09-30 23:04:50

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0930/64000.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。