CXX特性与语法记录

最近在读BakrN集成了Tensor Core的RISC-V GPGPU (vortex) 源码。当我看到这代码时,顿感压力山大。于是决定写下这篇笔记,记录一些C++的小特性和语法。

位域

struct float16{
  unsigned short sign     : 1;
  unsigned short exponent : 5;
  unsigned short mantissa : 10;
};

有如下内存分布:

 1 1       1 
 5 4       0 9                 0
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |exponent |     matissa       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~~~
 |
sign

C++

std::cout << "sizeof(float16): " << sizeof(float16) << std::endl;

Output

sizeof(float16): 2

所以,这个float16结构体事实上只占用了一个unsigned short的大小,16位。并且每一个段都有自己的名称,便于访问。

一个位域不可以具有超过其类型的长度,因此禁止出现unsigned int ui33: 33

一个位域不可以跨越基本类型的边界,如果跨越,则将该域对齐到下一个基本类型开始的位置:

struct inst1{
    unsigned int opcode: 6;
    unsigned int ui30  : 30;
};

有如下内存分布:

 3         3 3 3 2
 7         2 1 0 9                                                         0
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   opcode  |   |                          ui30                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
            ~~~~~
              |
            unused

C++

std::cout << "sizeof(inst1): " << sizeof(inst1) << std::endl;

Output

sizeof(inst1): 8

同样,也可以人工制定不具名块,将下一个域放置到下一个基本类型开始的位置:

struct inst2{
    unsigned int opcode: 6;
    unsigned int       : 0;
    unsigned int ui12  : 12;
};

C++

std::cout << "sizeof(inst2): " << sizeof(inst2) << std::endl;

Output

sizeof(inst2): 8

在上个例子中,opcodeui12即使长度小于一个基本类型(unsigned int),也还是会被分配到两个基本类型的空间中。

当然,请不要对其内部元素取址,C++编译器将会给你一个沉重的error。

enum class

枚举类enum class是C++11引入的语法,可以将枚举值限制在域内,禁止隐式转换,同时支持设定默认类型。

使用enum可能发生一些奇怪的事:

C++


enum class person{
  good = 0,
  bad
};

enum class colour{
  red = 0,
  blue
};

int main(int argc, char **argv) {
  std::cout << "good == red: " << (good == red) << std::endl;
  std::cout << "good == 0: " << (good == 0) << std::endl;
  return 0;
}

Output

good == red: 1
good == 0: 1

枚举值可以相互隐式转换,可能导致一些意料之外的状况。

当使用enum class时,枚举值被限制在class内,必须通过::访问。同时enum class禁止隐式转换,person::good == 0这类问题在编译时即可发现.

C++

enum class person{
  good = 0,
  bad
};

int main(int argc, char **argv) {
  std::cout << (person::good == 0) << std::endl;
  return 0;
}

Output

/main.cpp: In function 'int main(int, char**)':
/main.cpp:193:30: error: no match for 'operator==' (operand types are 'person' and 'int')
  193 |   std::cout << (person::good == 0) << std::endl;
      |                 ~~~~~~~~~~~~ ^~ ~
      |                         |       |
      |                         person  int

可变参数

可变参数...函数实际上是一个在C++中非常非常常见的东西,比如printf就是靠可变参数...实现的,因此printf可以接收任意个参数。即使格式化字符串不要求也是可以接收的:例如以下代码就是合法的。

printf("Nothing here", 1, 2, 3, 4, 5);

对于可变参数模板函数,花样就更多了:

template<int i, int i_end, typename Func, typename... Args>
inline void unrolled_for_func(Func &&func, Args &&... args) {
  if constexpr (i < i_end) {
    func(args...);
    unrolled_for_func<i + 1, i_end>(func, args...);
  }
}

以上代码可以执行i_end - iFunc(args),手动展开了for循环。例如

C++

int main(int argc, char **argv) {
  unrolled_for_func<0, 3>(printf, "This is a %s func call...\n", "printf");
  return 0;
}

Output

This is a printf func call...
This is a printf func call...
This is a printf func call...

如果你注意到了if后的constexpr,就会想到没有这个constexpr会发生什么?

C++

template<int i, int i_end, typename Func, typename... Args>
inline void unrolled_for_func(Func &&func, Args &&... args) {
  if (i < i_end) {
    func(args...);
    unrolled_for_func<i + 1, i_end>(func, args...);
  }
}

Output

/main.cpp: In instantiation of 'void unrolled_for_func(Func&&, Args&& ...) [with int i = 899; int i_end = 3; Func = int (&)(const char*, ...); Args = {const char (&)[27], const char (&)[7]}]':
/main.cpp:186:36:   recursively required from 'void unrolled_for_func(Func&&, Args&& ...) [with int i = 1; int i_end = 3; Func = int (&)(const char*, ...); Args = {const char (&)[27], const char (&)[7]}]'
/main.cpp:186:36:   required from 'void unrolled_for_func(Func&&, Args&& ...) [with int i = 0; int i_end = 3; Func = int (&)(const char*, ...); Args = {const char (&)[27], const char (&)[7]}]'
/main.cpp:192:26:   required from here
/main.cpp:186:36: fatal error: template instantiation depth exceeds maximum of 900 (use '-ftemplate-depth=' to increase the maximum)
  186 |     unrolled_for_func<i + 1, i_end>(func, args...);
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
compilation terminated.

编译器会因为超出递归深度限制而终止编译。由此可见,C++编译器会将所有出现的unrolled_for_func实例化,而不会检查i < i_end条件。当使用constexpr关键字限制后,编译器才会发现i < i_end这个限制条件,从而终止实例化unrolled_for_func

Reference

Nada, A., Sarda, G. M., & Lenormand, E. (2025). Cooperative Warp Execution in Tensor Core for RISC-V GPGPU. 2025 IEEE International Symposium on High Performance Computer Architecture (HPCA), pp. 1422–1436.

2025

在Illustrator中使用LaTeX

3 分钟阅读

在科研绘图中,我们可以充分发挥自己手边的工具:PowerPoint、Visio,甚至像RFC那样直接用ASCII字符作图。但当我们需要快速、精细绘制不区分领域的矢量图形时,Adobe Illustrator(AI)就可以发挥它最大的优势。 AI可以满足一大部分的科研矢量绘图需求,无论是流程图、示意图还是架构图,...

巢湖边,黄山下

3 分钟阅读

临时起意的行程似乎比计划已久的更易成行,因为这让我无法推迟决定去或不去。没有计划期望就更低,也不会因为早早规划好的行程不能如愿感到遗憾。

最后一次钟声

5 分钟阅读

多年以后,不知道我还会不会回想起那些离别的日子。 钟声 敲响最后一次钟声后,我从大学毕业了。 高中来,每逢开学与毕业,都会有人把一口大钟搬上主席台。只要它一响,就代表一批人来了,或是一批人走了。 得益于在学校电视台工作,前两年我参加了三届毕业典礼,送走了很多学长。 大二时,我参加了第一届毕业典礼,或者说...

五月鸣蜩

少于 1 分钟阅读

萱草生北堂 校史馆前的林阴路 ...

CXX特性与语法记录

9 分钟阅读

最近在读BakrN集成了Tensor Core的RISC-V GPGPU (vortex) 源码。当我看到这代码时,顿感压力山大。于是决定写下这篇笔记,记录一些C++的小特性和语法。 位域 struct float16{ unsigned short sign : 1; unsigned sh...

与星辰为伴

6 分钟阅读

天文台 每年1月初,象限仪座流星雨都会迎来极大值,而我还从来没有看到过一次流星,也就没能许过愿。今年终于有一段空闲时间,于是便早早规划起了追星之旅。毕竟在光污染极其严重的北京城区,是不太可能看到什么的。前几年的象限仪座流星雨又被称作鸽子座流星雨,不是不稳定就是因为月光影响。今年没有月光干扰,辐射点在半夜升起,虽...

返回顶部 ↑

2024

未选择的路

4 分钟阅读

三年前,我被北科机械专业录取。两年前,我转到了计科,同时被英语专业录取。一年后,我将会去北航,继续这漫漫求学路。 刚入大学,我度过了充满好奇的第一年。这一年,我遇到了许老师:她希望她的学生能够在自己所热爱的方向发展。一年过去,我拿到了大学里的第一个,也有第二和第三个国家级竞赛的奖项,还转成了专业,圆了与计算机差...

龙芯杯三日游

3 分钟阅读

连续一个月,或者说断断续续小半年的努力最终都在这一刻钟内展示出来。 Defence 在 14 世纪末有了“答辩”之意,我认为这和它更早就有的 protection 含义是类似的。不过一个是在理论上,保护自己的观点、意见,驳斥对方的;一个在物理上保护一个事物免受侵害。 线下的指令答题可以说人人都会,没有人会做不...

又是一年毕业季

2 分钟阅读

又是一年毕业季。23 年 6 月 16,最高温接近 40 度的一天,22 届的毕业生返校,与 23 届的毕业生一同参加这次毕业典礼。对我来说,这次毕业典礼和我的关系大概只是一次直播工作,我并不认识那些毕业的同学,也没什么特殊的感觉。只是看着他们都穿着学位服,和亲友合照。 24 年 6 月 21,今年的毕业典...

返回顶部 ↑