了解 LLVM IR,从手写开始

一、总览 如果你想要了解 LLVM,那么有几种可能呢?你或许是想要创造一门新的编程语言的技术爱好者;或是陷入编译课的泥沼不能自拔的大学生,但不管怎么样,你都只希望使用 LLVM 做一件事:生成目标代码。 但是这一过程并不容易。你大概率经过了编译前端的艰辛历程,但是现在在你面前的是另一座大山,LLVM IR。在最开始就看到什么基本块、虚拟寄存器、phi 节点之类的概念使你心烦意乱;各种各样的代码示例中充满了和重点并不相关的细节。这些都使你无从下手…… 或许一个更好的学习方式是将 LLVM IR 视为一种特殊的编程语言,而非编译器的 “中间表示”。这种语言站在了高级与低级语言之间,既具备了一定的抽象能力,又反映了底层汇编的工作原理。 本篇文章,就将以编程语言的视角粗略介绍 LLVM IR。通过直接手写 LLVM IR,逐步分析其特点和原理。希望能够有所帮助。 (1)准备工作 LLVM IR 虽然是中间代码,但是 LLVM 也提供了 lli 工具用于解释/即时编译执行 LLVM IR 文件。这样 LLVM IR 又与 Java 字节码有了一些相似之处。使用 lli 可以直接运行我们手写的中间代码,很是方便。使用 lli 这需要安装 LLVM。 sudo apt-get install llvm 另外,LLVM 中是没有内建输入输出的,因为 LLVM 的工作在操作系统之下。这就导致了一个问题,我们不能得知程序的执行结果。一种方法是可以用函数的返回值来输出,但是这样输出只能是单个数值。为了方便起见,可以先用 c 语言编写一个输出库。并使用 clang 将其编译成 LLVM IR。为此需要安装 clang sudo apt-get install clang 对于输出库,这里只简单定义 putint 函数。 // lib.c #include <stdio.h> void putint(int i) { printf("%d\n", i); } 使用 clang 将其翻译成中间代码。不要担心,我们并不需要关心生成的 lib....

十一月 3, 2023 · 17 分钟 · 3612 字 · Wokron

不务正业:玩个 zsh

一、前言 不知道为什么,这学期看到好几个人都用 zsh。最近恰好有时间,我也是时候换一个 shell 了。 不过嘛,shell 毕竟是用来工作的,不是一件艺术品,所以这里的配置也是以实用为主了。本文也没有用到 oh-my-zsh,用一个专门的框架来管理 shell 的配置在我看来还是太过沉重了。 本篇文章所用的环境当然是 linux。更加具体来说是 debian 系的发行版。 二、zsh 的安装 这里当然不会仔细介绍 zsh,将网上许多其他文章都重复过的内容再重复一遍。zsh 只是一个 shell,只不过这个 shell 有较强的可扩展性,同时具有一些比较有用的特性,仅此而已。 安装 zsh,只需要一条命令 apt-get install zsh 之后可以查看 /etc/shells 文件的内容,该文件记录了系统中已有的所有 shell,此时应当也有 zsh。 cat /etc/shells 本人的输出结果是这样的 # /etc/shells: valid login shells /bin/sh /bin/bash /usr/bin/bash /bin/rbash /usr/bin/rbash /usr/bin/sh /bin/dash /usr/bin/dash /bin/zsh /usr/bin/zsh 之后首次运行 zsh zsh 应该会出现如下内容,因为此时我们并未创建 zsh 的配置文件。正如 bash 的 .bashrc。这里只需要选择 0 即可。之后会在用户目录创建一个空的 .zshrc,该文件将在下一小节中详细说明。选择后重新输入命令 zsh 进入 zsh,此时应该可以看到输入提示符等内容。 This is the Z Shell configuration function for new users, zsh-newuser-install....

十月 21, 2023 · 4 分钟 · 663 字 · Wokron

面向对象的 C 语言

一、前言——对象与过程 碎碎念:这篇文章里提到的语言是真的多:c、c++、c#、java、python、golang c 语言怎么能面向对象呢?c 语言的设计当然并非为面向对象做出考虑,但是其拥有的语法却足以使我们写出具有面向对象味道的代码了。因为无论是面向过程或面向对象,其背后的本质思想都是相同的,那就是这样一个著名的公式: $$ 程序 = 数据结构 + 算法 $$ 面向过程无非是强调其算法的一面;面向对象无非是强调其数据结构的一面。当我们使用面向过程的思想编写代码时,我们所想的是数据是函数中的参数和变量,数据在过程中流动和变化。而在面向对象中情况则反了过来:方法成为了类的成员,被类型所划分,并从属于一定数据的集合。 了解了这一点之后,再看程序语言从面向过程到面向对象的发展过程也能有新的认识。这一发展背后实际上是程序的关注点由机器向人的转变。在面向过程的时代,人们所关注的是如何操纵数据。那时的机器还没有蒙在名为抽象的面纱之下,呈现在操作者面前的依旧是赤裸裸的整个内存空间,数据与数据之间没有清晰的边界,是操作者自己组织起整个系统,为各个空间划分边界,定下名称。而在这一构筑起来的系统之上,数据本身就没有那么重要了,因为更底层已经为其提供了随时取用的接口。这时,管理流程成了另一个关键问题。因为在底层的支持下构建起来的日益庞大的应用,其自身的结构却往往不能支持其质量。于是人们以数据为界,将面条一般的数据流切割成彼此独立却又相互关联的部分。这样对象才得以诞生。 二、c语言的面向对象何以可能 说回 c 语言,当其以结构体的方式组织起数据的时候,就已经有了对象的雏形了。如果我们将函数视为所属于其第一个参数类型的方法,那么对象的方法也可以表示。但是只有这两点并非真正的面向对象,因为面向对象的三大特征——封装、继承和多态,其中的后两者还未实现。 让我们来详细分析一下继承和多态到底在表明什么。继承是两个类型间的关系,类型 A 继承了类型 B,则类型 A 具有类型 B 所具有的一切属性和方法,这意味着对于 A 和 B 这两个不同的类型,都具有所属于 B 的部分。从这一点来说,两者是相同的(也因为这种相同,子类才能不加转换的赋给父类变量)。而多态(在这里指方法的重写而不包括重载)则指子类 A 对从 B 所继承的方法的重写,使得虽是相同方法,其表现却能有所不同。 明确了继承和多态,接下来我们从数据的角度分析 c 语言为何可以面向对象。所谓的一个对象,即在地址空间中的一段连续区域。此时继承中所谓的相同,即对两个不同类型的对象,其内存空间中相同位置所表达的含义相同。如果对于 B 类型来说,偏移 4 个字节之后的 4 个字节表示一个 int 字段,那么对于继承 B 类型的 A 类型来说,偏移 4 个字节之后的 4 个字节应同样表示一个 int 字段。类似的,多态中所谓的不同,可以表达为类型中相同的方法名对应的具体过程不同。由于过程在机器码中表现为地址,那么本质上来说,多态的这种不同不过是指相同字段中的值不同罢了。 此时 c 语言中实现继承和多态的方法呼之欲出,那就是使用指针。地址指示了变量所处空间的起始位置,却不表明按何种方式解释这块区域,而指针完成了这份工作。对于所有赋给指针的地址值,其都如实翻译其中的数据,那么如果想要子类与父类按照同样的方式进行翻译,就需要子类在组织其结构时保持和父类一致。而对于多态来说,事情则更简单了,函数指针同样是指针,只要使其指向不同的函数即可。 这样也可以理解为什么 c++ 中只能使用指针实现多态(引用本质还是指针)。 Child c; Father *f = &c; // 正确 Father f2 = c; // 错误 而 c++ 中使用 new 关键字申请内存这一点也被 java、c# 等面向对象语言学了过去。java、c# 等中的类变量,实际上也和指针或引用没有区别...

十月 1, 2023 · 5 分钟 · 962 字 · Wokron

CMake 实用语法教程

一、前言 最近一段时间在用 c++ 写一个项目,因此学了学 cmake。说实话,cmake 奇怪的语法在一开始实在容易让人望而生畏。但是上手使用的话就会发现平常会用到的不过是其中的一小部分,并且通常有规律可循。掌握这一部分的内容,大概率就可以组织起一个规模较大的项目了。因此本文也就旨在讲述 cmake 的这一部分的内容。 当然,阅读本教程之前需要了解代码编译、链接的相关知识。关于编译相关的命令,可见我的文章系统编程之命令行编译。 本文的所有代码保存在仓库 practical-cmake 中,欢迎 star :)。 cmake 下载方式如下(apt) sudo apt-get install build-essential sudo apt-get install cmake 二、第一步 说到第一个程序,那当然要请出经典的 hello, world 了。 #include <stdio.h> int main() { printf("hello, world\n"); return 0; } 在本文中我会首先给出使用 gcc 编译的命令,之后再使用 cmake 做同样的事情。那么对于第一步,我们的 gcc 命令如下 gcc main.cpp -o main 当然很简单,而对于 cmake 也类似。要使用 cmake,我们需要添加一个配置文件 CMakeLists.txt,其中包含要执行的操作。本小节中,CMakeLists.txt 的内容是 cmake_minimum_required(VERSION 3.10) project(main) add_executable(main main.cpp) 其中第一条指定了 cmake 版本要求,第二条指定了当前项目名,而第三条 add_executable 则实现了和 gcc 命令相同的操作:指定源文件 main.cpp 和输出文件名 main,生成一个可执行文件。...

九月 3, 2023 · 5 分钟 · 905 字 · Wokron

Pybind11 实现 Python 与 C++ 混合编程

一、前言 最近在尝试写一个简单的游戏引擎,我决定用 python 作为脚本,所以了解了一些混合编程的知识。 (1)python api 从原理来说,根据文档所述,python 提供了 Python.h 头文件,能够将 c 或 c++ 代码编译成可供 python 引入的动态链接库。该库中定义的可供 python 调用的函数中所有的入参都是名为 PyObject 结构体的指针。在代码中可以通过一系列函数对 PyObject 进行操作。 举一个简单的例子,我们希望 python 调用一个由 c 编写的简单的加法函数 int add(int a, int b) { return a + b; } 我们期望在 python 中这样调用 # test_mymodule.py from mymodule import add a = 10 b = 20 c = add(a, b) assert c == 30 那么我们首先需要对该函数进行包装,包装函数 _add 的参数和返回值都应该是 PyObject *。在包装函数中调用了 PyArg_ParseTuple 将传入的参数转换为 int 类型,调用原本的 add 函数得到返回值,之后又通过 PyLong_FromLong 将 int 转换为 PyObject。...

八月 17, 2023 · 3 分钟 · 631 字 · Wokron

Hexo 博客迁移教程

一、前言 因为用了新的笔记本,为了继续更新自己的博客,我决定把原来那台笔记本上的博客资源迁移过来。不过呢,当然不能用u盘拷贝这种比较low的方法,最好还是把资源放到 github 上,这样不仅方便现在的迁移,更能防止数据丢失。 二、将博客资源推送到仓库 如果你使用 hexo 搭建了自己的博客,并且把博客放到了 github 上,那么很容易注意到使用 hexo 部署时并不是将本地的所有内容推送到了 github,实际推送的只是 ./public 路径下的文件。而现在我们要做的就是将博客的所有资源推送到仓库,不仅是用于网页的部分。 我们选择就在博客网站所在的仓库存储博客资源,为了做到这一点,首先要在本地克隆一个仓库 git clone https://github.com/<username>/<username>.github.io.git 随后我们新建一个分支用于存储博客资源。该分支与博客网站所使用的 master 分支无关,因此最好创建成一个“孤儿”分支。 git checkout --orphan <branch_name> 切换到该分支后,原本随着克隆拉取到本地的文件现在依旧存在,需要将这些文件删除 git rm -rf . 接着将位于本地的博客资源复制到该文件夹下。 cp -r <old_blog_dir>/* . 这里需要注意,如果你使用了 next 等主题,并且是通过克隆仓库的方式下载的,那么此时应该把主题对应的项目路径下的 .git 文件夹删除。 # take next theme as example rm -r ./themes/next/.git 以上的工作都完成后,将这些复制到仓库中的博客资源文件添加并提交 git add . git commit -m "commit info" 最后将本地分支推送到远程仓库的新分支中 git push --set-upstream origin <remote_branch_name> 三、迁移博客 接下来要将博客迁移到另一台设备上。首先当然要下载 git 并配置用户名和邮箱 sudo apt install git git config --global user....

六月 26, 2023 · 2 分钟 · 268 字 · Wokron