Skip to content

Commit 03d3b69

Browse files
committed
upgrade undef more
1 parent cdfc582 commit 03d3b69

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed

docs/undef.md

+134
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,98 @@ i / j; // 错!
276276
i % j; // 错!
277277
```
278278

279+
## 求值顺序类
280+
281+
### 同一表达式内,对同一个变量有多个自增/自减运算
282+
283+
```cpp
284+
int i = 5;
285+
int j = (++i) + (++i); // j 的值未定义
286+
```
287+
288+
```cpp
289+
int j = (++i) + i; // j 的值未定义
290+
```
291+
292+
```cpp
293+
int i1 = 5;
294+
int i2 = 5;
295+
int j = (++i1) + (++i2); // j = 12
296+
```
297+
298+
### 内建类型的二元运算符,其左右两个参数求值的顺序是不确定的
299+
300+
在标准看来,+ 运算符两侧是“同时”求值的,即“interleaved”,实际执行顺序并不确定。
301+
302+
对于 a + b,我们不能假定总是左侧表达式 a 先求值。
303+
304+
不过,虽然运算符两个参数的求值顺序“未指定(unspecified)”,但并不是“未定义(undefined)”。
305+
306+
> 但左右两侧涉及自增/自减运算符的情况仍然是未定义行为。
307+
308+
```cpp
309+
int f1() {
310+
printf("f1\n");
311+
return 1;
312+
}
313+
314+
int f2() {
315+
printf("f2\n");
316+
return 2;
317+
}
318+
319+
int j = f1() + f2(); // 可能打印 f1 f2,也可能打印 f2 f1,但 j 最终的结果一定是 3
320+
```
321+
322+
未指定和未定义是不同的!有未定义行为的程序是非法(ill-formed)的,但未指定只是会让结果无法确定,但一定能正常运行:要么 f1 先运行,要么 f2 先运行。
323+
324+
### 函数参数求值的顺序是不确定的
325+
326+
```cpp
327+
int f1() {
328+
printf("f1\n");
329+
return 1;
330+
}
331+
332+
int f2() {
333+
printf("f2\n");
334+
return 2;
335+
}
336+
337+
void foo(int i, int j) {
338+
printf("%d %d\n", i, j);
339+
}
340+
341+
foo(f1(), f2()); // 可能打印 f1 f2 1 2,也可能打印 f2 f1 1 2
342+
```
343+
344+
代码中,f1 和 f2 的求值顺序虽然未指定,但可以保证 foo 函数体一定在执行完毕后才会开始。
345+
346+
同一条语句中所有子表达式的执行顺序就像一颗树,树中两个子节点执行顺序是不确定的;但可以肯定的是,树的子节点一定先于他们的父节点执行。
347+
348+
同样地,这只是未指定(unspecified)行为而不是未定义(undefined)行为,结果必然是 f1 f2 或 f2 f1 两种可能之一,不会让程序出现未定义值的情况。
349+
350+
注意,求值顺序未指定仅限同一语句(“同一行”)内,对于互相独立的多条语句,依然是有强先后顺序的。
351+
352+
```cpp
353+
int f1() {
354+
printf("f1\n");
355+
return 1;
356+
}
357+
358+
int f2() {
359+
printf("f2\n");
360+
return 2;
361+
}
362+
363+
void foo(int i, int j) {
364+
}
365+
366+
foo(f1(), f2()); // 可能打印 f1 f2,也可能打印 f2 f1
367+
368+
f1(); f2(); // 必然打印 f1 f2
369+
```
370+
279371
## 函数类
280372

281373
### 返回类型不为 void 的函数,必须有 return 语句
@@ -352,6 +444,38 @@ int main() {
352444
}
353445
```
354446
447+
### 函数指针被调用时,参数列表或返回值必须匹配
448+
449+
```cpp
450+
void f1(int *p) {
451+
printf("f1(%p)", p);
452+
}
453+
454+
void (*fp)(const int *);
455+
fp = (void (*)(const int *)) f1; // 错误
456+
457+
int i;
458+
fp = (void (*)(const int *)) &i; // 错误
459+
```
460+
461+
### 普通函数指针与成员函数指针不能互转
462+
463+
```cpp
464+
struct Class {
465+
void mf() {
466+
printf("成员函数\n");
467+
}
468+
};
469+
470+
union {
471+
void (Class::*member_func)();
472+
void (*free_func)(Class *);
473+
} u;
474+
u.member_func = &Class::mf;
475+
Class c;
476+
u.free_func(&c); // 错误
477+
```
478+
355479
## 生命周期类
356480
357481
### 不能读取未初始化的变量
@@ -504,6 +628,16 @@ std::string func() {
504628
}
505629
```
506630

631+
### 不能把函数指针转换为普通类型指针解引用
632+
633+
```cpp
634+
void func() {}
635+
636+
printf("*func = %d\n", *((int *)func)); // 错误
637+
```
638+
639+
> C++ 内存模型是哈佛架构(代码与数据分离),不是冯诺依曼架构(代码也是数据)
640+
507641
## 库函数类
508642
509643
### ctype.h 中一系列函数的字符参数,必须在 0~127 范围内(即只支持 ASCII 字符)

0 commit comments

Comments
 (0)