<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
            <title type="text">我的技术分享</title>
            <subtitle type="text">记忆就像苦海之中的沙砾，如果你不去挑拣，就永远沉在海底</subtitle>
    <updated>2024-02-26T21:45:03+08:00</updated>
        <id>https://www.midcheck.cn</id>
        <link rel="alternate" type="text/html" href="https://www.midcheck.cn" />
        <link rel="self" type="application/atom+xml" href="https://www.midcheck.cn/atom.xml" />
    <rights>Copyright © 2026, 我的技术分享</rights>
    <generator uri="https://halo.run/" version="1.5.2">Halo</generator>
            <entry>
                <title><![CDATA[Python FastAPI后台开发快速上手指南]]></title>
                <link rel="alternate" type="text/html" href="https://www.midcheck.cn/archives/pythonfastapi后台开发快速上手指南" />
                <id>tag:https://www.midcheck.cn,2024-02-26:pythonfastapi后台开发快速上手指南</id>
                <published>2024-02-26T21:40:04+08:00</published>
                <updated>2024-02-26T21:45:03+08:00</updated>
                <author>
                    <name>MidCHeck</name>
                    <uri>https://www.midcheck.cn</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>Python后台开发一般有三个流行的Web框架可以选择：<a href="https://docs.djangoproject.com/" target="_blank">Djanjo</a>、<a href="https://flask.palletsprojects.com/" target="_blank">Flask</a>和<a href="https://fastapi.tiangolo.com/" target="_blank">FastAPI</a>：</p><ul><li>Djanjo是一个重量级且功能比较全面的框架，但是学习曲线相比其他两个框架要高</li><li>Flask适合构建小中型网站后台和构建快速验证demo</li><li>FastAPI支持OpenAPI（Swagger），适合构建不需要前端页面的API后台</li></ul><p>关于三个框架的对比，网上有很多博客，在此不再赘述，本章分享一下FastAPI如何快速学习以及入门。</p><hr /><h1 id="%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF" tabindex="-1">学习路线</h1><ol><li>Python基础语法：<a href="https://www.runoob.com/python3/python3-data-type.html" target="_blank">菜鸟教程</a></li><li><a href="https://docs.python.org/zh-cn/3/library/asyncio.html" target="_blank">asyncio协程库</a> - 需要熟练掌握</li><li><a href="https://docs.aiohttp.org/en/stable/" target="_blank">aiohttp库</a> - 异步HTTP客户端，会用即可，在做异步网络请求时非常方便</li><li><a href="https://fastapi.tiangolo.com/zh/tutorial/" target="_blank">FastAPI官方教程</a> - 按顺序阅读，跟着官方教程做下去即可，进阶学习可以阅读FastAPI源码</li></ol><p>基本了解了这些就能开发出FastAPI后台代码了，剩余的在使用过程中多debug就能熟悉</p><h1 id="fastapi%E9%87%8D%E7%82%B9" tabindex="-1">FastAPI重点</h1>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[内核篇1：docker + Qemu搭建内核开发与调试环境]]></title>
                <link rel="alternate" type="text/html" href="https://www.midcheck.cn/archives/内核篇1搭建内核开发与调试环境" />
                <id>tag:https://www.midcheck.cn,2023-04-20:内核篇1搭建内核开发与调试环境</id>
                <published>2023-04-20T17:50:36+08:00</published>
                <updated>2023-04-25T13:16:21+08:00</updated>
                <author>
                    <name>MidCHeck</name>
                    <uri>https://www.midcheck.cn</uri>
                </author>
                <content type="html">
                        <![CDATA[<p>俗话说，工欲善其事必先利其器，有一个好用的开发与调试环境对内核学习来说事半功倍，因此在这里记录一下内核开发环境的搭建。</p><h2 id="%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85" tabindex="-1">开发环境安装</h2><p>开发环境我选择使用基于docker的ubuntu环境，ubuntu对开发友好，开发过程中常用的库也都能使用包管理工具apt下载到，而且该docker环境支持的架构也比较多，很适合跨平台开发，<a href="https://github.com/docker-library/official-images#architectures-other-than-amd64" target="_blank">支持的架构</a>如下:<br /><img src="/upload/2023/04/image.png" alt="image" /></p><h3 id="%E5%AE%89%E8%A3%85docker" tabindex="-1">安装docker</h3><p>docker的安装方法不用赘述，网上教程很多，以Ubuntu为例，这里选择docker.io安装，其他系统也有可能是<a href="https://docs.docker.com/get-docker/" target="_blank">Docker Desktop</a>，安装步骤如下：</p><pre><code class="language-"># 1.安装docker.iosudo apt-get update  # 更新源,防止源中的下载连链接失效sudo apt-get install docker.io# 2.使普通用户可以使用docker,避免输入sudosudo groupadd dockersudo usermode -aG docker $USER# 2.给docker换国内源,加速docker pullsudo mkdir -p /etc/dockerecho &#39;{  &quot;registry-mirrors&quot;: [          &quot;https://docker.mirrors.ustc.edu.cn&quot;,          &quot;http://hub-mirror.c.163.com&quot;,          &quot;https://registry.docker-cn.com&quot;  ]}&#39; &gt; daemon.jsonsudo mv daemon.json /etc/docker/# 3.重启docker daemonsudo systemctl restart docker# 4.拉取Ubuntu22.04的镜像docker pull ubuntu:22.04# 5.查看是否安装成功docker images | grep ubuntu | grep 22.04</code></pre><h3 id="%E7%BC%96%E8%AF%91%E5%86%85%E6%A0%B8" tabindex="-1">编译内核</h3><pre><code class="language-">apt-get install wget xz-utils -y # 安装wget下载工具和xz压缩工具cd /root &amp;&amp; wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.108.tar.xztar -xf linux-5.15.108.tar.xz # 解压源码#安装编译环境和依赖库apt-get install libncurses-dev flex bison bc openssl libssl-dev dkms libelf-dev libudev-dev libpci-dev libiberty-dev autoconf gcc make gnu-standards libtool gettextcd linux-5.15.108 # 进入源码目录# 生成基于文本界面的内核配置菜单,该操作依赖ncurses库# 当出现’Your display is too small to run Menuconfig!‘提示时,将终端放大重新执行这条命令即可make menuconf## 配置开启KGDB参数,然后光标选择save,将选项保存到.config文件中## Kernel hacking ---&gt;##    Compile-time checks and compiler options ---&gt;##        [*] Compile the kernel with debug info##        [*] Provide GDB scripts for kernel debugging##    Generic Kernel Debugging Instruments ---&gt;##        [*] KGDB: kernel debuggermake -j $(nproc)   # 进行编译,此处nproc变量为cpu核心的个数</code></pre><h3 id="%E5%88%B6%E4%BD%9C%E6%A0%B9%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F" tabindex="-1">制作根文件系统</h3><pre><code class="language-"># 安装依赖apt-get install cpio unzip rsync# 下载buildrootwget https://github.com/buildroot/buildroot/archive/refs/tags/2022.02.11.tar.gztar -xf buildroot-2022.02.11.tar.gzcd buildroot-2022.02.11# 配置buildrootmake menuconfig## Target options ---&gt;##     Target Architecture ---&gt;##         (X) x86_64## Toolchain ---&gt;##     Toolchain type ---&gt;##         (X) Buildroot toolchain##     C library ---&gt;##         (X) glibc##     Kernel Headers##         (X) Linux 5.15.x kernel headers##     [*] Install glibc utilities##     [*] Enable C++ support##     [*] Build cross gdb for the host##     [*] TUI support##     [*] Python support##     [*] Simulator support## Filesystem images ---&gt;##     [*] ext2/3/4 root filesystem##         ext2/3/4 variant ---&gt;##             (X) ext4## Bootloaders ---&gt;##     [*] grub2##     [ ] i386-pc##     [ ] i386-efi##     [*] x86-64-efi##     (boot linux ext4 fat squash4 part_msdos part_gpt normal efi_gop) builtin modulesmake -j $(nproc) # 开始编译</code></pre><h3 id="%E8%BF%90%E8%A1%8C%E5%86%85%E6%A0%B8" tabindex="-1">运行内核</h3><p>将编译后的内核压缩文件bzImage和根文件系统rootfs.ext4拷贝至具有qemu运行环境的宿主机中运行:</p><pre><code class="language-">mkdir kernel &amp;&amp; cd kerneldocker cp ubuntu:/root/buildroot-2022.02.11/output/images/rootfs.ext2 .docker cp ubuntu:/root/linux-5.15.108/arch/x86/boot/bzImage .</code></pre><p>使用qemu-system-x86_64运行内核，当有图形界面时，执行：</p><pre><code class="language-">sudo qemu-system-x86_64 -kernel ./bzImage -hda rootfs.ext2 -append &quot;root=/dev/sda console=ttyS0 nokaslr&quot; -serial stdio</code></pre><p>没有图形界面时，将-serial stdio替换为-nographic即可。启动成功后会在终端中输出banner和登录提示，登录名为root，密码默认为空（buildroot进行make menuconfig配置时设置），如下图所示：<br /><img src="/upload/2023/04/image-1682398116423.png" alt="image-1682398116423" /></p><h3 id="%E4%BD%BF%E7%94%A8gdb%E6%8C%82%E8%BD%BD%E8%B0%83%E8%AF%95%E5%86%85%E6%A0%B8" tabindex="-1">使用gdb挂载调试内核</h3><p>使用qemu的’-s’和’-S’参数使内核可以被gdb挂载调试，执行命令如下：</p><pre><code class="language-">sudo qemu-system-x86_64 -kernel ./bzImage -hda rootfs.ext2 -append &quot;root=/dev/sda console=ttyS0 nokaslr&quot; -serial stdio -s -S</code></pre><p>‘-s’: 让qemu监听在tcp::1234端口上，可以让gdb连接该端口进行调试<br />‘-S’: 让qemu启动后暂停执行，直到通过gdb传入继续执行(continue)命令</p><p>在ubuntu的容器里，安装gdb，执行以下命令挂载qemu，对内核进行调试：</p><pre><code class="language-">root@824b1b82dfae:~# gdb /root/linux-5.15.108/vmlinux(gdb) add-auto-load-safe-path /root/linux-5.15.108/scripts/gdb/vmlinux-gdb.py(gdb) target remote 172.17.0.1:1234Remote debugging using 172.17.0.1:12340x000000000000fff0 in exception_stacks ()(gdb) cContinuing.</code></pre><p>此处的172.17.0.1替换为qemu执行后，监听tcp 1234端口的ip。</p><p>在gdb中可以使用Ctrl + C来暂停qemu，然后就可以正常进行调试了。</p>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[vmware中Linux与宿主机共享剪贴板失效问题]]></title>
                <link rel="alternate" type="text/html" href="https://www.midcheck.cn/archives/虚拟机共享剪贴板失效问题" />
                <id>tag:https://www.midcheck.cn,2022-09-21:虚拟机共享剪贴板失效问题</id>
                <published>2022-09-21T10:05:45+08:00</published>
                <updated>2022-09-21T10:12:35+08:00</updated>
                <author>
                    <name>MidCHeck</name>
                    <uri>https://www.midcheck.cn</uri>
                </author>
                <content type="html">
                        <![CDATA[<h2 id="%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88" tabindex="-1">解决方案</h2><ol><li>查看是否安装open-vm-tools</li><li>查看本发行版是否具有并安装open-vm-tools-desktop</li><li>普通用户权限执行vmware-user，即可解决</li></ol><h2 id="%E9%97%AE%E9%A2%98%E5%8F%82%E8%80%83" tabindex="-1">问题参考</h2><ol><li><a href="https://bbs.archlinux.org/viewtopic.php?id=268549" target="_blank">vmware-user-suid-wrapper requires restart of vmtoolsd</a> （未有答复)</li><li><a href="https://blog.csdn.net/joshuaxx316/article/details/44901477" target="_blank">VMware Workstation中客户机与主机之间无法复制文件和剪切板问题解决</a> (问题及解决方法）</li><li><a href="https://wiki.archlinux.org/title/VMware_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)/Installing_Arch_as_a_guest_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)#%E6%8B%96%E6%8B%BD%E4%B8%8E%E5%A4%8D%E5%88%B6%E7%B2%98%E8%B4%B4" target="_blank">VMWare拖拽与复制粘贴</a> (archlinux wiki)</li></ol>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[Rust学习1-数据类型]]></title>
                <link rel="alternate" type="text/html" href="https://www.midcheck.cn/archives/rust学习1-数据类型" />
                <id>tag:https://www.midcheck.cn,2022-04-30:rust学习1-数据类型</id>
                <published>2022-04-30T01:00:09+08:00</published>
                <updated>2022-04-30T03:17:59+08:00</updated>
                <author>
                    <name>MidCHeck</name>
                    <uri>https://www.midcheck.cn</uri>
                </author>
                <content type="html">
                        <![CDATA[<blockquote><p>Rust有两种<a href="https://doc.rust-lang.org/book/ch03-02-data-types.html" target="_blank">数据类型</a>：标量值（<em>scalar</em>）和复合值（<em>compound</em>）。</p></blockquote><p>本篇环境为Ubuntu22.04，x86_64，Rust版本为1.60.0。</p><h2 id="%E6%A0%87%E9%87%8F%E5%80%BC" tabindex="-1">标量值</h2><p>标量类型标识一个单独的值，Rust有4种主要的标量类型：整型、浮点型、布尔类型、字符类型。</p><p>这四种类型与Perl中的Scalar Literals概念类似，都是字面值，所见即所得。</p><p>Rust默认数据类型为4字节的i32，与C/C++保持一致。</p><h3 id="%E6%95%B4%E5%9E%8B" tabindex="-1">整型</h3><p>与C/C++中定义相同，分为有符号与无符号两种，支持加、减、乘、除、模运算。</p><p>整数除法与C/C++一致，向下取余，例如2/3 == 0。</p><table><thead><tr><th style="text-align:center">字节数</th><th style="text-align:center">比特数</th><th style="text-align:center">Rust</th><th style="text-align:center">C/C++</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td style="text-align:center">8</td><td style="text-align:center">i8/u8</td><td style="text-align:center">char/unsigned char</td></tr><tr><td style="text-align:center">2</td><td style="text-align:center">16</td><td style="text-align:center">i16/u16</td><td style="text-align:center">short/unsigned short</td></tr><tr><td style="text-align:center">4</td><td style="text-align:center">32</td><td style="text-align:center">i32/u32</td><td style="text-align:center">int/unsigned int</td></tr><tr><td style="text-align:center">8</td><td style="text-align:center">64</td><td style="text-align:center">i64/u64</td><td style="text-align:center">long long/unsigned long long</td></tr><tr><td style="text-align:center">4/8</td><td style="text-align:center">32/64</td><td style="text-align:center">isize/usize</td><td style="text-align:center">long/unsigned long</td></tr></tbody></table><p>isize和usize属于自适应类型，根据架构来决定大小，在32位架构就是4字节，64位架构为8字节，对应gcc编译器下的long和unsigned long类型，微软的msvc编译器没有这一特性，需要用宏来判断。自适应类型非常好用，尤其是在C/C++中用内存地址与指针进行互转时。</p><p><s>或许MSVC不支持自适应类型的原因之一就是Windows只能在有限的架构上运行？（笑）</s></p><h4 id="%E6%95%B4%E5%9E%8B%E8%A1%A8%E7%A4%BA" tabindex="-1">整型表示</h4><p>为了便于阅读，不用怼着屏幕数数字个数，Rust可以使用’_'字符作为数字的分隔符。</p><table><thead><tr><th>字面值</th><th>例子</th></tr></thead><tbody><tr><td>十进制</td><td>98_222</td></tr><tr><td>十六进制</td><td>0xff</td></tr><tr><td>八进制</td><td>0o77</td></tr><tr><td>二进制</td><td>0b1111_0000</td></tr><tr><td>字节</td><td>b’A’</td></tr></tbody></table><p>字节类型为单字节字符，仅限于u8，与Python中的Bytes完全不同。</p><h4 id="%E6%95%B4%E5%9E%8B%E6%BA%A2%E5%87%BA%E9%97%AE%E9%A2%98" tabindex="-1">整型溢出问题</h4><p>在debug模式编译下，Rust会检查并使程序panic，而release模式下，Rust不检测溢出，会执行2的补码包装（<em>two’s complement wrapping</em>）操作，例如在u8类型下，值256会变成0，257变成1，即</p><pre><code class="language-">value = value % 2^(sizeof(value)*8)</code></pre><p>整型溢出在Rust中被认为是一种错误，但如果编程时确实需要使用溢出，可以在数字类型上使用标准库中的Wrapping功能：</p><ul><li>包装所有模式：wrapping_*，例如wrapping_add</li><li>返回None值：checked_*</li><li>返回布尔值：overflowing_*，指示是否发生溢出</li><li>饱和处理：saturating_*，在边界上饱和处理成最大或最小值</li></ul><p><a href="https://docs.rs/num/0.1.42/num/trait.Saturating.html" target="_blank">饱和处理</a>，对数字值进行计算时，发生上溢时返回最大值，下溢时返回最小值，无符号最小值为0，有符号时值为0 - （2^n-1）。</p><pre><code class="language-">/* 函数签名 */pub trait Saturating {     fn saturating_add (self, v: Self) -&gt; Self;// 返回a+b，溢出时返回该类型的最大值    fn saturating_sub (self, v: Self) -&gt; Self;  // 返回a-b，溢出时返回该类型的最小值}</code></pre><h3 id="%E6%B5%AE%E7%82%B9%E5%9E%8B" tabindex="-1">浮点型</h3><p>Rust浮点类型使用IEEE-754标准表示，有两种浮点类型，f32和f64，分别占4字节和8字节，对应C/C++中的float和double，所有的浮点类型都是有符号的。</p><p>Rust认为在现代CPU上f32与f64速度相当，但精度更高，所以默认类型是f64，与Python相同（如果是在32位芯片上，个人还是建议使用f32）。</p><pre><code class="language-">let x = 2.0;// f64let y: f32 = 3.0;// f32</code></pre><h3 id="%E5%B8%83%E5%B0%94%E5%9E%8B" tabindex="-1">布尔型</h3><p>类型名称为bool，分为true和false，常用于条件表达式中。</p><h3 id="%E5%AD%97%E7%AC%A6%E5%9E%8B" tabindex="-1">字符型</h3><p>Rust的字符型数据使用4个字节存储，表示为char，与C/C++中一样使用单引号，字符串字面值使用双引号。</p><p>字符型表示一个Unicode代码点，范围为U+0000到U+D7FF，U+E000到U+10FFFF。</p><p>有点好奇Rust中正则的用法了。</p><h2 id="%E5%A4%8D%E5%90%88%E7%B1%BB%E5%9E%8B" tabindex="-1">复合类型</h2><p>Rust有两个原生复合类型：元组（<em>tuple</em>）和数组（<em>array</em>）。</p><h3 id="%E5%85%83%E7%BB%84%E7%B1%BB%E5%9E%8B" tabindex="-1">元组类型</h3><p>元组使用圆括号，是长度固定的数组（C系语言概念），可以包含一个或多个类型的值，第一个元素的索引值为0。</p><pre><code class="language-">// 示例函数fn main() {  // 使用类型注解  let tup: (i32, f64, u8) = (500, 6.4, 1);   // 可以省略类型注解  let tup = (500, 6.4, 1);    // 使用模式匹配（pattern matching）对元组进行解构（destructure）  let (x, y, z) = tup;    // 使用点号&#39;.&#39;跟索引值来直接访问  let five_hundred = x.0;  let six_point_four = x.1;  let one = x.2;}</code></pre><p>没有任何元素的元组是一种特殊类型，表示为( )，只有一个值，和表示相同，写成( )，被称为单位类型（<em>unit type</em>），值被称为单位值（<em>unit value</em>）。如果表达式没有显示地返回值，则隐式地返回该单位值。</p><h3 id="%E6%95%B0%E7%BB%84%E7%B1%BB%E5%9E%8B" tabindex="-1">数组类型</h3><p>数组使用方括号，Rust中数组长度是固定的，并且每个值的类型都相同。</p><pre><code class="language-">let a = [1, 2, 3, 4, 5];let a: [i32; 5] = [1, 2, 3, 4, 5];   // 带注解的数组类型写法let a = [3, 3, 3, 3, 3,];// 初始值为5个3let a = [3; 5];// 初始值为5个3，与上一行效果相同// 确定了元素个数不会改变时，建议使用数组let months = [&quot;January&quot;, &quot;February&quot;, &quot;March&quot;, &quot;April&quot;, &quot;May&quot;, &quot;June&quot;, &quot;July&quot;,              &quot;August&quot;, &quot;September&quot;, &quot;October&quot;, &quot;November&quot;, &quot;December&quot;];// 数组是在栈上分配已知固定大小的内存块// 可以使用索引访问let first = a[0];left second = a[1];</code></pre><h4 id="%E6%95%B0%E7%BB%84%E7%9A%84%E6%A0%88%E6%BA%A2%E5%87%BA" tabindex="-1">数组的栈溢出</h4><p>当数组在运行时发生索引溢出时，程序会退出并输出错误信息。Rust在使用数组时，会在编译时和运行时都检查索引是否小于数组长度，当编译时发现溢出，编译器会报错，运行发生溢出时，程序会panic。</p><p>此原则会尽量避免像C/Python/Java等语言中忘记检查输入范围而导致栈溢出漏洞情况的出现。</p><h5 id="%E7%BC%96%E8%AF%91%E6%97%B6%E6%A3%80%E6%9F%A5%3A" tabindex="-1">编译时检查:</h5><pre><code class="language-">// 示例代码main.rsfn main() {    let a = [1, 2, 3, 4, 5];        let five = a[5];    println!(        &quot;The six value of the element at index {}&quot;,        five    );}</code></pre><p>进行编译：</p><pre><code class="language-">xxx@ubuntu:~/Rust/array$ cargo build   Compiling array v0.1.0 (/home/xxx/Rust/array)error: this operation will panic at runtime --&gt; src/main.rs:5:16  |5 |     let five = a[5];  |                ^^^^ index out of bounds: the length is 5 but the index is 5  |  = note: &#96;#[deny(unconditional_panic)]&#96; on by defaulterror: could not compile &#96;array&#96; due to previous error</code></pre><h5 id="%E8%BF%90%E8%A1%8C%E6%97%B6%E6%A3%80%E6%9F%A5%3A" tabindex="-1">运行时检查:</h5><pre><code class="language-">use std::io;fn main() {    let a = [1, 2, 3, 4, 5];    println!(&quot;Please enter an array index.&quot;);    let mut index = String::new();    io::stdin()        .read_line(&amp;mut index)        .expect(&quot;Failed to read line&quot;);    let index: usize = index        .trim()        .parse()        .expect(&quot;Index entered was not a number&quot;);    let element = a[index];    println!(        &quot;The value of the element at index {} is: {}&quot;,        index, element    );}</code></pre><p>当编译后运行时，输入的值为10时，报错如下：</p><pre><code class="language-">thread &#39;main&#39; panicked at &#39;index out of bounds: the len is 5 but the index is 10&#39;, src/main.rs:19:19note: run with &#96;RUST_BACKTRACE=1&#96; environment variable to display a backtrace</code></pre>]]>
                </content>
            </entry>
            <entry>
                <title><![CDATA[C语言数组的非常量声明]]></title>
                <link rel="alternate" type="text/html" href="https://www.midcheck.cn/archives/c语言数组的非常量声明" />
                <id>tag:https://www.midcheck.cn,2022-04-26:c语言数组的非常量声明</id>
                <published>2022-04-26T00:48:07+08:00</published>
                <updated>2022-04-30T03:19:44+08:00</updated>
                <author>
                    <name>MidCHeck</name>
                    <uri>https://www.midcheck.cn</uri>
                </author>
                <content type="html">
                        <![CDATA[<h2 id="%E5%89%8D%E8%A8%80" tabindex="-1">前言</h2><p>在<a href="https://c-cpp.com/c/language/array" target="_blank">C语言中文网</a>中关于C数组类型有三类：</p><blockquote><p>已知常量大小的数组、变长度数组，以及未知大小数组。</p></blockquote><p><em><strong>变长度数组即本文所讨论的非常量声明，未知大小数组在函数传数组参数时较为常见，此外还有0长大小的柔性数组（C99之前依赖编译器特性）。</strong></em></p><h2 id="%E8%83%8C%E6%99%AF" tabindex="-1">背景</h2><p>在阅读shadowsocks-libev源码时，发现有这么一行:</p><pre><code class="language-">// initialize listen contextlisten_ctx_t listen_ctx_list[server_num];</code></pre><p>在以往的知识里，声明数组大小时必须是常量表达式，然而此处发现却与以往认知相左，于是便有了本文的探究。</p><h2 id="%E6%B5%8B%E8%AF%95" tabindex="-1">测试</h2><p>测试环境为ubuntu20.04，x86_64，gcc 9.4.0，测试代码如下:</p><pre><code class="language-">#include &lt;stdio.h&gt;int main(int argc, char **argv){    int num;    scanf(&quot;%d&quot;, &amp;num);    int array[num];    printf(&quot;%d\n&quot;, sizeof(array)/sizeof(array[0]));    return 0;}</code></pre><p>结果:</p><pre><code class="language-">$ gcc -o test test.c$ ./test1010$ ./test-1-1</code></pre><p>证明此种声明方式确实有效。添加编译标志-std=gnu89，更换编译器为gcc 4.8.4，32位，都可以编译通过，并且正常运行（输入负数打印结果不一致不在本文讨论）。</p><p>在ss-libev源码中，此行代码在任何编译器宏下都能到达，意味着此方式声明数组不与特定编译器相关。</p><h3 id="%E5%85%B6%E5%AE%83%E5%8F%91%E7%8E%B0" tabindex="-1">其它发现</h3><p>不能在声明时初始化数组，如下代码：</p><pre><code class="language-">int array[num] = {0};</code></pre><p>编译器报错信息如下:</p><pre><code class="language-">$ gcc -o test test.ctest.c:6:5: error: variable-sized object may not be initialized</code></pre><hr /><p>那么这种变长度数组又是如何实现的呢，查看一下它的汇编。</p><h2 id="%E6%B1%87%E7%BC%96%E5%88%86%E6%9E%90" tabindex="-1">汇编分析</h2><p>使用gdb查看该段代码汇编（intel语法）：</p><pre><code class="language-">$ gcc -o test test.c -g</code></pre><pre><code class="language-">   0x00000000080011ba &lt;+49&gt;:    lea    rax,[rbp-0x4c]      # rbp-0x4c处为变量num的地址   0x00000000080011be &lt;+53&gt;:    mov    rsi,rax   0x00000000080011c1 &lt;+56&gt;:    lea    rdi,[rip+0xe3c]        # 0x8002004   0x00000000080011c8 &lt;+63&gt;:    mov    eax,0x0   0x00000000080011cd &lt;+68&gt;:    call   0x8001090 &lt;__isoc99_scanf@plt&gt;=&gt; 0x00000000080011d2 &lt;+73&gt;:    mov    ecx,DWORD PTR [rbp-0x4c]   # 将num值放入ecx寄存器中   0x00000000080011d5 &lt;+76&gt;:    movsxd rax,ecx                 # 以下算法不知作用，待补充   0x00000000080011d8 &lt;+79&gt;:    sub    rax,0x1   0x00000000080011dc &lt;+83&gt;:    mov    QWORD PTR [rbp-0x48],rax   0x00000000080011e0 &lt;+87&gt;:    movsxd rax,ecx   0x00000000080011e3 &lt;+90&gt;:    mov    r14,rax   0x00000000080011e6 &lt;+93&gt;:    mov    r15d,0x0   0x00000000080011ec &lt;+99&gt;:    movsxd rax,ecx   0x00000000080011ef &lt;+102&gt;:   mov    r12,rax   0x00000000080011f2 &lt;+105&gt;:   mov    r13d,0x0   0x00000000080011f8 &lt;+111&gt;:   movsxd rax,ecx   0x00000000080011fb &lt;+114&gt;:   lea    rdx,[rax*4+0x0]   0x0000000008001203 &lt;+122&gt;:   mov    eax,0x10   0x0000000008001208 &lt;+127&gt;:   sub    rax,0x1   0x000000000800120c &lt;+131&gt;:   add    rax,rdx   0x000000000800120f &lt;+134&gt;:   mov    edi,0x10   0x0000000008001214 &lt;+139&gt;:   mov    edx,0x0   0x0000000008001219 &lt;+144&gt;:   div    rdi                   # 以下将分配的栈与4k对齐   0x000000000800121c &lt;+147&gt;:   imul   rax,rax,0x10   0x0000000008001220 &lt;+151&gt;:   mov    rdx,rax   0x0000000008001223 &lt;+154&gt;:   and    rdx,0xfffffffffffff000   0x000000000800122a &lt;+161&gt;:   mov    rsi,rsp   0x000000000800122d &lt;+164&gt;:   sub    rsi,rdx   0x0000000008001230 &lt;+167&gt;:   mov    rdx,rsi   0x0000000008001233 &lt;+170&gt;:   cmp    rsp,rdx   0x0000000008001236 &lt;+173&gt;:   je     0x800124a &lt;main+193&gt;   0x0000000008001238 &lt;+175&gt;:   sub    rsp,0x1000           # 如果分配大于4k则栈抬高4k   0x000000000800123f &lt;+182&gt;:   or     QWORD PTR [rsp+0xff8],0x0   0x0000000008001248 &lt;+191&gt;:   jmp    0x8001233 &lt;main+170&gt;   0x000000000800124a &lt;+193&gt;:   mov    rdx,rax              # 栈4k块分配完毕   0x000000000800124d &lt;+196&gt;:   and    edx,0xfff   0x0000000008001253 &lt;+202&gt;:   sub    rsp,rdx          # 栈抬高4k以内的大小，num小于4k时就是num   0x0000000008001256 &lt;+205&gt;:   mov    rdx,rax         0x0000000008001259 &lt;+208&gt;:   and    edx,0xfff   0x000000000800125f &lt;+214&gt;:   test   rdx,rdx          # 当num大于4k时，   0x0000000008001262 &lt;+217&gt;:   je     0x8001274 &lt;main+235&gt;   0x0000000008001264 &lt;+219&gt;:   and    eax,0xfff   0x0000000008001269 &lt;+224&gt;:   sub    rax,0x8   0x000000000800126d &lt;+228&gt;:   add    rax,rsp   0x0000000008001270 &lt;+231&gt;:   or     QWORD PTR [rax],0x0  # 检查栈位置能否有读写权限   0x0000000008001274 &lt;+235&gt;:   mov    rax,rsp         0x0000000008001277 &lt;+238&gt;:   add    rax,0x3     # array数组4字节对齐   0x000000000800127b &lt;+242&gt;:   shr    rax,0x2   0x000000000800127f &lt;+246&gt;:   shl    rax,0x2   0x0000000008001283 &lt;+250&gt;:   mov    QWORD PTR [rbp-0x40],rax   # 将数组的地址放在array变量里   0x0000000008001287 &lt;+254&gt;:   movsxd rax,ecx      # 计算sizeof大小并准备printf的参数   0x000000000800128a &lt;+257&gt;:   shl    rax,0x2              0x000000000800128e &lt;+261&gt;:   shr    rax,0x2   0x0000000008001292 &lt;+265&gt;:   mov    rsi,rax   0x0000000008001295 &lt;+268&gt;:   lea    rdi,[rip+0xd6b]        # 0x8002007   0x000000000800129c &lt;+275&gt;:   mov    eax,0x0   0x00000000080012a1 &lt;+280&gt;:   call   0x8001080 &lt;printf@plt&gt;</code></pre><p>虽然0x80011d5 - 0x800120c处不知道是什么作用， 但从其他部分代码处可以看到运行时动态分配了数组空间。</p><h2 id="%E7%BB%93%E8%AE%BA" tabindex="-1">结论</h2><p>在栈上动态分配数组方式确实有效，但使用时需严格注意该数组作用域。</p>]]>
                </content>
            </entry>
</feed>
