注意到 enum 可以有 underlying type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum no_underlying {
a0
};
int main(void) {
int a = _Generic(a0,
int: 2,
unsigned char: 1,
default: 0
);
int b = _Generic((enum no_underlying)a0,
int: 2,
unsigned char: 1,
default: 0
);
return a + b;
}

此时 a 一定是 2,没有 underlying type 的枚举值默认是 int。而 b 是实现定义的:enum 的 underlying type 只要能容纳其枚举值即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum underlying : unsigned char {
a0
};
int main(void) {
int a = _Generic(a0,
int: 2,
unsigned char: 1,
default: 0
);
int b = _Generic((enum no_underlying)a0,
int: 2,
unsigned char: 1,
default: 0
);
return a + b;
}

保证一定是 1。

注意到 restrict qualifier

image-20250827013334753

像是一种优化提示,告诉编译器我这些指针指向的内容在函数运行期间不会被其他的任何途径访问到。

image-20250827013430592

场景:数组在函数当中不会被访问

image-20250827013540660

Restriction:只能从作用域内向外访问。

image-20250827013815713

例外是作为返回值:

image-20250827014023957

详见 6.7.3.1。大半夜读读剩下来几个例子我有点 注意力涣散

注意力涣散 函数声明

image-20250827015649287

我以前还真的没有想过,对数组名取地址到底有什么效果。(其实就是普通的取地址)

image-20250827015919054 image-20250827020340055

It’s pointer to int[3],i.e. int (*)[3]

scanf 似乎接受 char s[] 字符串的地址 &s,以及字符串的指针 s。它们只是值相同而类型不同。前者会给出类型不匹配的 warning。

注意到 这个可以用来作 C 语言课程的教学内容

image-20250827020801413

还是在想,是不是 C 语言课程应该首先科普 “指针” 的概念?然后再教别的。在这之前,首先要介绍 “内存” 是怎么工作的。。。

第一节课,应该是数据:类型和数组——的机器和 C 语言表示,以及少量 io。所有东西都简化到内存中去,不考虑寄存器。

image-20250827021642377

initializer 也会有踩踏。

注意到 standard attributes

image-20250827021900324 image-20250827023056250

这看起来很能函数式。还有 [[unsequenced]]。明天来写个详细的。

是不是以后算 fib 都不用记忆化存储的?

–答案是不能。看似这个 attr 只能优化掉编译期可以推导出的参数,而不是动态地 cache 相同参数的结果。testcase:

1
2
3
4
5
6
7
8
9
10
11
/* foo.c */
int foo(int n) {
return n + 42;
}

/* bar.c */
int foo(int n)[[reproducible]];

int main() {
return foo(0) + foo(0);
}

如果没有 [[reproducible]] 声明,在编译单元 bar.c 中不知道 foo 是纯函数,为了保留副作用,会进行两次调用。声明之后,有两次 foo(0) 纯函数调用,会被合并为一次。

结果:只调用了一次

0124 = 84 = 42 + 42

如果 foo 的调用参数里有运行期变量的话,就一次也无法优化。具体的例子是:

1
2
for (int i = 0; i < 10; i++) foo(i);
for (int i = 1; i < 11; i++) foo(i);

这样会调用 20 次而不是推导出两个循环里有相同参数的调用。

要观察到这个现象不能放在一个文件里。否则常量折叠会直接干碎所有调用。另外注意添加 -O2/-O3 优化,毕竟 attr 是为了优化而生的。

检查编译器是否支持:

1
static_assert(__has_c_attribute(reproducible) == 202311L);   // 写在代码任意位置

注意到 C23 支持了 #embed,编译期嵌入二进制文件。

image-20250827215953347

哈哈,数学公式爆了

image-20250827220402199

使用例:

image-20250827220617836

这个编译期嵌入随机数的例子还挺有意思的:

image-20250827220804831 image-20250827221836799

limit 参数将影响 if_empty 的判断。

注意力涣散 感觉很复杂的 __VA_OPT__ 但是一时不知道该怎么用

image-20250827223058235 image-20250827223249347 image-20250827223303835

看了第二个例子感觉有点懂了。如果 ... 存在的话,就把 __VA_OPT__ 的参数放进去。没有的话就不放。

这不是构造函数吗(?)

1
2
3
4
#define H5A(...) __VA_OPT__(0)/**/__VA_OPT__(1)
#define H5B(X) a ## X ## b
#define H5C(X) H5B(X)
H5C(H5A(1)) // replaced by a0 1b

最后一个例子换一下,中间就会多一个空格。说明每一个 __VA_OPT__ 都会产生一个单独的 pp-tokens 而不是连成一个。

注意力集中 ## == # ## # != ##

image-20250827225010738

注意到 assert() 将会被 NDEBUG 宏 disable

image-20250827231726116

有这样的一个 pragma:

image-20250827232634911

可以让用户保证类型安全的情况下用这些公式进行计算。

image-20250827233027320

复变反三角函数是一个多值函数。因而要 cut 它的一个解析分支出来

image-20250827233051809

cacos 剪掉了实轴上 [-1, +1] 以外所有的定义域,只保留主值。 (表达可能不精准,我没咋学过复变)

其他三角函数也类似。

注意到 <math.h> 中的函数除非文档显式说明了,否则保证不会抛出 SIGFPE

image-20250828001650146

?

image-20250828003521915

注意到 这些函数可以对齐浮点数的阶数

image-20250828010344968

并有判断函数

image-20250828010442529

前面还有函数能够 “返回下一个可表示的数”,大概是用于数值计算一类的应用或者是 debug 浮点实现。

安全的浮点数比较:

image-20250828010718800

明天来开 setjmp.h