>> >> >> Reference << << << <<<<<<Ref>>>>>>
>> >> >> Indexer << << << <<<<<<Idx>>>>>>
Matched: 0

Tags

    Categories

      Types

        Top Results

          Value
          M: 2025-12-31 - ljf12825

          Variable

          定义: C中的变量 = 一个有名字的,可寻址的存储区域 + 一组由类型定义的解释规则

          拆开来看

          维度含义
          名字符号(symbol),供程序员与编译器使用
          存储区域实实在在的内存(或寄存器)
          类型决定:大小,对齐,读写方式,解释方式

          变量不是值,值只是变量在某一刻内存中的比特状态

          概念含义
          标识符(identifier)名字本身,比如x
          对象(object)一块存储区域
          变量(variable)具名对象(有名字的object)
          int x = 10;
          
          • x:标识符
          • int x:定义了一个对象
          • x:这个对象是变量(具名对象)

          变量的本质分类维度

          C中变量不是靠一种方式分类的,而是多维度叠加的

          按存储期(Storage Duration)分类(核心)

          这是理解C的第一关键点

          自动存储期(automatic)

          void f() {
              int x = 10;
          }
          
          特性说明
          分配时间进入作用域时
          释放时间离开作用域时
          典型位置
          默认初始化未初始化(垃圾值)

          静态存储期(static)

          static int x;
          int y; 
          
          特性说明
          生命周期整个程序运行期间
          分配位置.data/.bss
          默认初始化初始化 0
          作用域看声明位置

          包括:全局变量,static局部变量,文件级static

          动态存储期(dynamic)

          int* p = malloc(sizeof(int));
          

          | 特性 | 说明 | | 分配 | 运行时 | | 释放 | free | | 管理者 | 程序员 | | 存储位置 | 堆 |

          p是自动变量,*p指向的对象是动态存储期

          按作用域(Scope)分类

          块作用域(block scope)

          {
              int x;
          }
          

          只在{}内可见

          文件作用域(file scope)

          int g;
          

          从声明点到文件末尾

          函数原型作用域

          void f(int x);
          

          x只在参数列表中有效

          按链接属性(Linkage)分类

          这是编译/链接阶段的核心概念

          链接属性说明
          无链接仅在当前作用域
          内部链接当前翻译单元
          外部链接跨翻译单元
          static int x;
          int y;
          

          链接属性决定:符号是否能被Id看见

          按类型(Type)分类(值的解释规则)

          int x;
          float y;
          struct S s;
          int* p;
          

          类型决定

          • 占用字节数
          • 对齐方式
          • 运算规则
          • 是否可修改
          • 是否可别名(alias)

          类型是“解释规则”,不是内存本身

          定义 vs 声明

          定义(Definition)

          分配存储空间

          int x;
          

          声明(Declaration)

          告诉编译器“它存在”

          extern int x;
          

          一个变量只能定义一次,可以声明多次

          初始化的本质

          自动变量

          int x; // 未初始化
          // 读取未初始化的自动对象的值是未定义行为
          

          静态变量

          static int x; // 自动初始化为0
          

          原因不是“语法规则”,而是:静态存储期变量在程序加载时由运行时系统清零

          变量与内存

          int x = 10;
          

          本质

          地址 A:
          0000000A 00 00 00 
          
          • x -> 符号 -> 地址A
          • 10 -> 比特模式
          • int -> 如何解释这4个字节

          C中的变量是:由编译器管理的符号 + 存储期 + 作用域 + 链接属性 + 类型解释规则,本质是对一段内存的而语言级约束

          Constant

          C语言中没有“真正不可变的变量”这个概念
          常量 != 只读内存,常量 != const

          在C中,“常量”是一个语义概念,而不是一个统一的实体

          C中的常量分类

          字面量(Literal Constant)

          42
          3.14
          'a'
          "hello"
          

          本质:编译期就确定的值

          分类

          类型示例存储
          整数字面量42, 0xFF通常直接嵌入指令
          浮点字面量3.14只读数据段
          字符字面量'a'整型常量
          字符串字面量"hello"静态存储区(只读)
          char *p = "hello";
          p[0] = 'H'; // 未定义行为
          

          字符串字面量不是数组变量,而是静态只读对象

          #define宏常量

          #define N 100 
          

          本质:预处理阶段的纯文本替换

          int a[N]; // 等价于 int a[100];
          

          特点:

          • 不占用存储
          • 无类型
          • 无作用域(作用于文本范围)
          • 不是调试器友好

          编译器根本不知道它“是常量”

          const修饰的对象

          const int x = 10;
          

          const不是“常量”,而是“通过该名字不可修改”
          也就是说

          const int x = 10;
          
          • x是一个变量
          • 有存储
          • 可能被放在只读区,也可能不

          反例

          int y = 20;
          const int *p = &y;
          *p = 30; // 编译错误
          y = 30; // 合法
          

          const约束的是访问路径(lvalue),不是对象本身

          enum枚举常量

          enum { A = 1, B = 2 };
          
          • A, B是编译期整型常量
          • 不占内存
          • 可用于数组长度、case标签
          int arr[A]; // 合法
          

          在很多底层代码中,enum被当作类型安全的宏常量

          常量和变量在内存中的真实样子

          典型内存布局

          .text 代码
          .rodata 字符串字面量 / const 对象(可能)
          .data 已初始化全局变量
          .bss 未初始化全局变量
          .stack 自动变量
          .heap 动态分配
          

          C标准并不规定const一定放在只读区,这是编译器行为,不是语言保证

          初始化 vs 赋值

          int x = 10; // 初始化
          x = 20; // 赋值
          
          const int n = 10;
          int arr[n]; // 在C中不一定合法,C90/C99不允许,C99VLA或C11开启VLA允许
          

          原因

          • n不是编译期常量
          • 即使n的值不变,它仍是运行期对象

          GCC扩展可能允许,但这不是标准C

          编译期 vs 运行期

          • 编译期:程序还没有变成可执行文件之前,所有“由编译器完成的计算与决策”
          • 运行期:程序已经开始执行,由CPU + 操作系统参与的所有行为

          编译期行为

          严格说,编译期 != 只有compiler,它是一个阶段链

          1. 预处理期(Preprocessing)

          #define N 10 
          #include <stdio.h> 
          

          发生的事情:

          • 宏替换
          • 条件编译
          • 头文件展开

          这一阶段

          • 没有类型
          • 没有变量
          • 没有作用域
          • 只是文本

          2. 编译期(狭义的compiler)

          编译器做的事

          • 词法/语法分析
          • 语义分析(类型检查)
          • 常量折叠(const folding)
          • 生成中间表示(IR)
          • 优化
          • 生成目标代码(.o)
          int x = 3 * 4 + 5; // 编译期算成17 
          
          enum { N = 16 };
          int a[N]; // OK 
          

          const int n = 16;
          int a[n]; // 标准C不行,n是运行期对象,不是编译期常量
          

          3. 链接期(Linking)

          • 符号解析
          • 地址重定位
          • 合并段
          extern int g;
          

          此时

          • 才知道g的最终地址
          • 才能确定跨文件符号

          运行期

          运行期开始于

          程序被 OS 加载
          main() 开始执行
          

          运行期发生的事情

          • 栈帧建立/销毁
          • 变量分配
          • 函数调用
          • 分支跳转
          • 内存读写
          • 系统调用
          • 线程调度

          总结

          永远问三个问题

          1. 这东西有没有对象
          2. 它的值是不是在编译时就确定
          3. 是否需要运行时指令来获得

          只要第3个是YES,那就是运行期

          编译期决定“形状”,运行期决定“状态”

          • 编译期:代码长什么样
          • 运行期:代码怎么跑
          问题编译期运行期
          是否有 CPU 指令在跑
          是否有内存地址部分(符号)全部
          是否能访问变量值
          是否能调用函数
          是否能分配栈
          是否能决定数组大小✅(需常量表达式)
          是否能优化代码

          从汇编角度的一句判断法:只要需要生成“运行时指令去算”的,就是运行期

          int f(int n) {
              int a[n]; // 运行期分配
          }
          

          编译器必须生成

          • 计算n
          • 调整rsp
          • 恢复栈

          编译器、编辑器、语言语义、程序分析边界

          编辑器/IDE可以在编辑期发现一部分编译器必然错误,但原则上无法在编辑期可靠地发现依赖运行期状态的错误\

          为什么编辑器能报编译期错误

          编译器并不是猜,而是在调用编译器能力
          现代编辑器(VS Code, CLion, Vim + clangd)做的事情是

          • 实时分析源码
          • 运行语法分析 + 语义分析
          • 使用与编译器相同或相似的前端

          例如

          int x = "abc"; // 类型错误
          

          这是一个不依赖输入,不依赖环境,对所有执行路径都成立的错误
          这种错误在任何运行期都会失败 -> 可在编辑期100%确定

          这类错误的共同特征

          • 不需要执行程序
          • 不依赖输入
          • 不依赖分支
          • 对所有路径成立

          因此,编辑器可以提前报错,本质是静态分析

          为什么编辑器不能报出运行期错误

          典型例子

          int f(int x) {
              return 10 / x;
          }
          

          问题

          • x == 0 -> 崩溃
          • x != 0 -> 正常

          编辑器在编辑器无法知道

          • x是来自哪里
          • 用户输入是什么
          • 走哪条分支

          这是一个理论上的不可能性,这里不是“技术不够好”,而是计算理论极限
          核心问题:停机问题(Halting Problem):不存在一个程序,能够对任意程序,判断它是否在运行时发生错误

          运行期错误本质上是

          • 程序行为
          • 状态演化
          • 动态路径

          这些都属于不可完全静态判定的问题

          为什么有时编辑器好像能发现运行期问题

          编辑器有时候会提示空指针、越界等行为,这是因为,编辑器做的是保守静态分析
          例如

          int *p = NULL;
          *p = 10;
          

          这是100%必然的运行期错误,不需要输入,不存在分支,所有路径必崩,因此可以在编辑期报错

          但一旦引入条件,就不再是”必然“

          if (p != NULL)
              *p = 10;
          

          是否安全取决于p的来源,取决于执行路径;编辑器此时只能给warning或保持沉默

          编辑器/编译器的”能力边界“

          能做到的(静态、确定性)

          • 语法错误
          • 类型错误
          • 必然的未定义行为
          • 明显越界(常量索引)
          • 明显空指针解引用

          做不到的(动态、路径相关)

          • 输入相关崩溃
          • 数据竞争(大多数情况)
          • 逻辑错误
          • 时序问题
          • 资源泄漏(完全精确)

          这也就是为什么需要”运行期工具“,既然编辑期/编译期有理论极限,工程上怎么解决

          1. 运行期检测(动态分析)

            • AddressSanitizer
            • Valgrind
            • ThreadSanitizer

            它们的特点:需要执行,基于真实路径,有性能开销(插桩)

          2. 类型系统作为”前移防线“ 例如const, restrict,不可空指针(其他语言),能在编译期消灭一类运行期错误