2.1 对象 (Object)
1. 概念
对象的定义
对象是执行环境中数据存储的一个区域,其内容可以表示值。对象指那块存储本身,与源代码里用什么名字访问它无关:同一个对象可以被不同表达式访问,也可以暂时没有可见标识符(例如通过指针间接访问,或在复合字面量中创建)。
2. 对象的关键属性
对象的三组关键属性
每个对象至少包含三组关键属性:类型、存储期和值。类型决定这段存储如何被解释,存储期决定它何时创建与销毁,值表示当前状态。
3. 对象与标识符
标识符是源代码中的记号;对象是执行期实体。二者常常一一对应,也有例外:结构体成员没有单独的顶层标识符,却仍然对应对象;同一块存储也可以通过不同标识符间接访问(第 8 章指针一节会展开此话题)。
#include <stdio.h>
int main(void) {
int a = 10;
int b = a;
a = 15;
printf("a = %d, b = %d\n", a, b);
return 0;
}2
3
4
5
6
7
8
9
10
可能的输出(示例):
a = 15, b = 10
4. 对象状态与不确定值
对象创建后不一定有可读的值
对象创建后不一定马上就有可读的值。自动存储期对象若未初始化,其值是不确定的,读取它通常没有语义保障;动态分配存储在写入前也没有有效值。
5. 习题
阅读下面的程序,先写出输出,再回答问题。
#include <stdio.h>
int main(void) {
int a = 4;
int b = a;
int c = b;
a = 9;
b = a + 1;
printf("%d %d %d\n", a, b, c);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
main的函数体里声明了几个int对象?a、b、c三个名字与这些对象是什么关系?- 写出输出,并说明
c为什么保持 4。 - 把
b = a + 1;改成b = b + 1;,输出是什么?这一行读的是哪个对象? - 按第 2 节的三组属性,写出
c在printf执行前的类型、存储期和值。
验收:原程序输出 9 10 4,改动后输出 9 5 4;第 4 问应说明 c 是自动存储期、类型为 int、值为 4。
下面的程序想累加两次读数。用 gcc -std=c17 -Wall -Wextra 编译并运行。
#include <stdio.h>
int main(void) {
int total;
int reading = 5;
total = total + reading;
total = total + reading;
printf("total = %d\n", total);
return 0;
}2
3
4
5
6
7
8
9
10
11
(1) [2.1] 抄下编译器的完整警告,指出是哪个对象、哪一行触发的。
(2) [3.2] 本机运行输出 total = 10。这个结果为什么不能说明程序正确?读取一个未初始化的自动对象,标准给了什么保证?
(3) [3.2] 给出两种修改:一种在声明处初始化,一种在第一次使用前赋值。两种都要求 -Wall -Wextra 下没有任何输出,且运行结果为 total = 10。
(4) [4.3**] 把累加的对象换成 malloc 得到的一块存储:包含 <stdlib.h>,写 int *p = malloc(sizeof *p);,不写入就直接读 *p 并累加,最后调用 free。GCC 13.2 的提示是 '*p' may be used uninitialized [-Wmaybe-uninitialized],把警告与修改前后的输出都记下来。这一问用到第 8 章和第 11 章的内容,可以跳过。
验收:前三问中,修改后的程序在 -Wall -Wextra 下没有任何输出,运行打印 total = 10。
下面的程序想把三个对象的当前值轮换:执行后 a 得到 b 的原值,b 得到 c 的原值,c 得到 a 的原值。
#include <stdio.h>
int main(void) {
int a = 1;
int b = 2;
int c = 3;
a = b;
b = c;
c = a;
printf("%d %d %d\n", a, b, c);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
- 先写出输出,再运行核对。轮换完成了吗?哪一步覆盖了后面还要用的值?
- 只增加一个临时对象,写出正确的轮换,输出应为
2 3 1。 - 不借助任何临时对象,也不使用加减、异或等运算,只用赋值能否完成这个轮换?从对象保存的是值而不是名字的角度说明理由。
- 把同样的做法用在四个对象
w、x、y、z上(初值依次为 1、2、3、4),要求w得到z的原值、x得到w的原值、y得到x的原值、z得到y的原值。连续执行两轮并打印,验证两轮的结果。
验收:第 2 问输出 2 3 1;第 4 问第一轮后为 4 1 2 3,第二轮后为 3 4 1 2。
下面的程序用指针访问同一个对象,也构造了没有名字的对象。指针的完整规则在第 8 章,这里只按给出的写法照做。
#include <stdio.h>
int main(void) {
int live = 21;
int *alias = &live;
int copy = live;
*alias = 25;
copy = copy + 1;
printf("%d %d %d\n", live, *alias, copy);
alias = ©
*alias = 30;
printf("%d %d %d\n", live, *alias, copy);
printf("%d\n", alias == &live);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 先写输出再核对。第一组输出里
live和*alias为什么相同? - 第二次打印时
*alias与copy相同,而live不变。此时alias指向哪个对象?两个printf之间,程序里的对象总数变了吗? int *p = &(int){7};构造出的对象有名字吗?写出程序打印*p、再通过p改成 10 后再次打印的结果,并说明这块存储在什么时候存在。- 把第 3 问的写法用两次,分别写成
int *p = &(int){7};和int *q = &(int){7};,打印p == q。预测结果并运行验证;再写int *r = p;后打印r == p。两次比较的结果为什么不同? - 用一段话说明:为什么说“程序操作的是对象,而不是名字”,用本题中的
live、alias、copy与那个没有名字的对象各举一例。
验收:第 1、2 问的两次打印依次为 25 25 22 与 25 30 30,最后一行打印 0;第 3 问打印 7,改成 10 后再打印 10;第 4 问 p == q 为 0,r == p 为 1。