分类 C/C++ 下的文章

呜呜呜,C语言白学了.jpg

今天看C4P(C++ Primer Plus)才发现两个东西:

  1. define不是全文替换,而是单词替换;

假设有如下代码段:

#include ...
#define test "sss"
...
    char s[] = test;
    float tested = 1;
    printf("%s test\n", s);
...

输出是sss test而不是sss "sss"。(C++支持连续字符串自动拼接) (如果要验证define会不会替换变量名的话,得要至少两个文件才行)

  1. C语言的字符常量是按照int大小(通常是4Bytes)存储的。C++早期也是,后来才改成了和char一致的大小(按int存储的话会导致cout以数字形式输出字符常量的ASCII码)。

这操作太神奇了,完全没有想到还有这种妙用

这个表达式主要用在宏定义和条件分支两种情况:

一是宏定义的时候,可以避免由多个语句组成的宏,在没有{}的控制语句下产生错误的语义,如:

#define func() funcA(); funcB()

...

if(cond) func();

就会导致funcAfuncBcond == false的情况下产生错误的语义(原本计划是两条语句都不执行,而现在funcB必定执行)。

简单直观的解释:

if(cond)
    funcA();
funcB();

do {...} while(0)(注:这里while后面没有分号,目的是符合C/C++在语句后添加分号的风格,在调用宏的时候再加)则会被系统认为是一个单独的语句块,在面对分支结构的时候,就能够被正确地视作原子操作进行处理。

为什么不用{},而要用更复杂的do {...} while(0)呢?

直接上代码:

if(cond) { funcA(); funcB(); };

这个时候,原本在func末尾的分号成了累赘,反而会导致报错。这也是出于保持代码风格一致的考虑。

由于宏函数不存在返回值一说,而多语句的宏函数显然不会、也不应该产生返回值,所以无须担心把这类宏函数放到赋值语句中会怎么样——这和void类型的函数的行为是一致的。

二是简化条件汇集的情况。考虑这么一种情况:

if(condA) { funcA(); }
if(condB) { funcA(); funcB(); }
if(condC) { funcA(); funcB(); funcC(); }

如果真的在代码里这么写的话,想必是一座宏伟的代码金字塔了。随着条件增加,重复而又意义不大的函数体,会变得越来越长。于是有人考虑使用goto语句,合并重复的语句:

if(!condA) goto end;
funcA();

if(!condB) goto end;
funcB();

if(!condC) goto end();
funcC();

end:
    ...

但是对软件工程有所了解的人都会知道,在代码中使用goto是一种不清真的行为,会对代码的可读性和入口的单一性产生负面影响,影响后续的开发和维护工作。

继续观察,注意到上面例子中所有的情况下,只要到了某一条件不满足,就直接跳转到整个代码块的末尾。有什么语句可以跳过块内所有的剩余语句,立即跳出语句块?答案呼之欲出,break!于是,do {...} while(0); 作为能够使指令只执行一遍的循环结构,再次登场:

do {
    if(!condA) break;
    funcA();

    if(!condB) break;
    funcB();

    if(!condC) break;
    funcC();
} while(0);

这样既继承了原始代码不使用不规范函数的原则,又吸收了goto版本缩减重复代码的优点。
当然,如果有人想ifif,就当我没说

参考:http://www.spongeliu.com/415.html

参考文章中还提到了定义空宏和代码块功能,感觉这两个有点牵强。。定义空宏的话直接用分号应该也行,代码块功能也和循环语句没有必然的联系。

参考OpenMPI的文档

Rank可以理解为进程的编号。

CUDA里最基本的线程逻辑结构是顺序的,像一维数组一样,有一个连续递增的线程号(threadIdx.x),且总线程数是线程可知的(blockDim.x)。
MPI也有这两个特性(事实上MPI(1991)要先于CUDA(2007),而我只是恰好先了解的CUDA),不过命名...就没有CUDA那么友好了:使用MPI_Comm_size(...)获取总进程数,通常记为comm_sz这不说谁看得懂啊;使用MPI_Comm_rank获取当前进程编号,通常记为my_rank。这种表记方式来自Peter S. Pacheco的《并行程序设计导论》。

MPI_Init(int *argc, char ***argv)在调用任何MPI函数前调用,类似于初始化的作用

MPI_Send()阻塞性函数,对面不接受就一直阻塞

MPI_Sendrecv(const void *sendbuf, int sendcount, MPI_Datatype sendtype,
    int dest, int sendtag, void *recvbuf, int recvcount,
    MPI_Datatype recvtype, int source, int recvtag,
    MPI_Comm comm, MPI_Status *status)

由于MPI_SendMPI_Recv是阻塞性函数,由于线程竞争或者不恰当的程序逻辑很容易导致死锁,MPI_Sendrecv可以解决这个问题:同时进行发送和接收消息两个动作。

参数依次是: 发送区域起始地址 / 发送数据数量 / 发送数据类型
(向...发送数据的)目标进程编号 / (似乎是)发送的消息编号 / 接收区域起始地址 / 接收数据最大数量
接收数据类型 / (从...接收数据的)源进程编号 / (似乎是)想接收的消息编号
(MPI通讯器?不清楚) / (MPI状态?不清楚

关于Sendrecv这个博客的例子比较简洁易懂。
没仔细看,先丢在这里,关于send/recv tag的:https://stackoverflow.com/questions/31386659/when-to-use-tags-when-sending-and-receiving-messages-in-mpi

MPI_Finalize()(尚不清楚是否一定要求调用)清除进程中所有的MPI状态量,类似于退出MPI环境,在该进程中,之后MPI的函数库中只能调用MPI_Get_version, MPI_Initialized, MPI_Finalized三个函数。

==== 2020/1/8 开题 期末考试终于考!完!了!呼哈哈哈哈哈哈。。哈哈。。哈哈哈。。。(笑岔气.jpg)

然后就到了半年一度的自主项目时间。去年冬天填了一个Java的坑,拿AWT写了个贪吃蛇(不要问,问就是挑战自我),当时下学期要学Java,就正好提前动了一下手;去年夏天因为实验室的工作给机器学习开了个头,看了部分传统方法(线性/Sigmoid函数)的原理,现在稍微想想发现已经忘的差不多了

然后今年冬天的计划还是填坑。去年冬天在RPCS3的Discord里看到有人询问想参与开发该怎么入手,群里Dalao就提到了一个叫CHIP8的东西。然后查了一下,发现是一个非常早期的游戏平台。Dalao说如果是为了入门模拟器的话,可以自由选择一门语言实现它。当时本来是想用Java的,后来想了一下当时编程能力比现在不知道弱到哪里去了虽然现在也蔡的不行,然后正好今年就用C++来实现一下,正好接触一下Qt框架。

UI框架的选择也是看了半天,听说GTK写起来晦涩难懂,wxWidgets可以在修改少量代码的情况下实现跨平台,最后还是选择了庞大的Qt。(光是安装库就用了近2G空间= =)

然后第一坑就是VS2019+Qt插件的配置。 -> Qt + VS2019配置踩坑

唉,没想到今年最后一天竟然在写这个。8说了,过两天还有考试呢。。

  1. 使用glGetUniformLocation获取定义的着色器uniform,输入两个参数:glCreateProgram()返回的标识值,和定义的uniform的名称(string形式)。如果在任一着色器内都没有使用过所定义的uniform的话,GLSL编译器会自动删除该变量,这时返回值是-1,对应unsigned int的值是4294967295。如果使用了错误的programId值也是一样的。

  2. 与着色器相关的函数调用序列如下:
        glCreateProgram()
        glAttachShader()
        glLinkProgram()
        glUseProgram()
        glGetUniformLocation()
        glUniformMatrix4fv()

    来自StackOverflow