编程迷思(一)

What I can not create, I do not understand.

1. 一切皆对象 = 一切皆指针

容器有两种:命名容器和匿名容器。命名容器指的是能够和某个变量名绑定的容器。匿名容器指的是仅能通过指针指向的在堆上分配空间的容器。

对于Java(不含简单类型,简单类型还是能存在栈上的)、python这些一切皆对象的语言来说

  • 所有简单容器都是命名的
  • 所有结构化容器(存储于堆上的类实例对象)都是匿名的
  • 指针仅指向结构化容器
  • 结构化容器仅包括了简单容器

由于所有变量名绑定的都是简单容器,因此访问对象的时候都是通过指针访问,其实也就意味着“一切皆对象”等价于“一切皆指针”。

因此java中的a.b.c.d仅意味着通过指针访存多次。而c语言的a.b.c.d意味着

  1. 通过指针访存多次
  2. 对任意一个.是通过指针访存,或是基于偏移量,例如一个极端例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

struct A {
  struct B {
    struct C {
      int d;
    } c;
  } b;
} a;

int main() {
 a.b.c.d = 100;
 printf("a.b.c.d: %d\n", a.b.c.d);
 return 0;
}

然而由于不存在动态内存分配器分配的过程,栈上分配通常比堆上分配要快很多。

2. 未定义行为(UB)和内存安全

C99中对UB的定义如下:

2.1. undefined behavior

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

《programming rust》中对类型安全给出的定义是:如果语言的安全性检查能够确保每一个程序都不会出现未定义行为,则可以认为该语言是类型安全的语言

所谓类型,就是一类数据的集合,并在其上定义有被允许的一系列操作。操作的结果也有明确的结果类型。换言之,编译器能够识别任何操作。UB的发生may be编译器并不能识别所有可能发生操作的结果类型,例如对于a[-1]操作,c语言编译器就不能给出该运算的结果类型。

这种类型判断可能发生在编译期也可能发生在运行时,例如:

在python中数组越界访问a[1000],就会抛出IndexError异常。也即python定义了该操作的结果类型是异常而非数组对应类型。