2022年5月

本文是 Go 系列的第三篇文章,我将介绍三种最流行的复制文件的方法。

本文将介绍展示如何使用 Go 编程语言 来复制文件。在 Go 中复制文件的方法有很多,我只介绍三种最常见的:使用 Go 库中的 io.Copy() 函数调用、一次读取输入文件并将其写入另一个文件,以及使用缓冲区一块块地复制文件。

方法一:使用 io.Copy()

第一种方法就是使用 Go 标准库的 io.Copy() 函数。你可以在 copy() 函数的代码中找到它的实现逻辑,如下所示:

func copy(src, dst string) (int64, error) {
  sourceFileStat, err := os.Stat(src)
  if err != nil {
    return 0, err
  }

  if !sourceFileStat.Mode().IsRegular() {
    return 0, fmt.Errorf("%s is not a regular file", src)
  }

  source, err := os.Open(src)
  if err != nil {
    return 0, err
  }
  defer source.Close()

  destination, err := os.Create(dst)
  if err != nil {
    return 0, err
  }
  defer destination.Close()
  nBytes, err := io.Copy(destination, source)
    return nBytes, err
  }

首先,上述代码做了两个判断,以便确定它可以被打开读取:一是判断将要复制的文件是否存在(os.Stat(src)),二是判断它是否为常规文件(sourceFileStat.Mode().IsRegular())。剩下的所有工作都由 io.Copy(destination, source) 这行代码来完成。io.Copy() 函数执行结束后,会返回复制的字节数和复制过程中发生的第一条错误消息。在 Go 中,如果没有错误消息,错误变量的值就为 nil

你可以在 io 包 的文档页面了解有关 io.Copy() 函数的更多信息。

运行 cp1.go 将产生以下输出:

$ go run cp1.go
Please provide two command line arguments!
$ go run cp1.go fileCP.txt /tmp/fileCPCOPY
Copied 3826 bytes!
$ diff fileCP.txt /tmp/fileCPCOPY

这个方法已经非常简单了,不过它没有为开发者提供灵活性。这并不总是一件坏事,但是,有些时候,开发者可能会需要/想要告诉程序该如何读取文件。

方法二:使用 ioutil.WriteFile() 和 ioutil.ReadFile()

复制文件的第二种方法是使用 ioutil.ReadFile()ioutil.WriteFile() 函数。第一个函数用于将整个文件的内容,一次性地读入到某个内存中的字节切片里;第二个函数则用于将字节切片的内容写入到一个磁盘文件中。

实现代码如下:

input, err := ioutil.ReadFile(sourceFile)
if err != nil {
  fmt.Println(err)
  return
}

err = ioutil.WriteFile(destinationFile, input, 0644)
if err != nil {
  fmt.Println("Error creating", destinationFile)
  fmt.Println(err)
  return
}

上述代码包括了两个 if 代码块(嗯,用 Go 写程序就是这样的),程序的实际功能其实体现在 ioutil.ReadFile()ioutil.WriteFile() 这两行代码中。

运行 cp2.go,你会得到下面的输出:

$ go run cp2.go
Please provide two command line arguments!
$ go run cp2.go fileCP.txt /tmp/copyFileCP
$ diff fileCP.txt /tmp/copyFileCP

请注意,虽然这种方法能够实现文件复制,但它在复制大文件时的效率可能不高。这是因为当文件很大时,ioutil.ReadFile() 返回的字节切片会很大。

方法三:使用 os.Read() 和 os.Write()

在 Go 中复制文件的第三种方法就是下面要介绍的 cp3.go。它接受三个参数:输入文件名、输出文件名和缓冲区大小。

cp3.go 最重要的部分位于以下 for 循环中,你可以在 copy() 函数中找到它,如下所示:

buf := make([]byte, BUFFERSIZE)
for {
  n, err := source.Read(buf)
  if err != nil && err != io.EOF {
    return err
  }
  if n == 0 {
    break
  }

  if _, err := destination.Write(buf[:n]); err != nil {
    return err
  }
}

该方法使用 os.Read() 将输入文件的一小部分读入名为 buf 的缓冲区,然后使用 os.Write() 将该缓冲区的内容写入文件。当读取出错或到达文件末尾(io.EOF)时,复制过程将停止。

运行 cp3.go,你会得到下面的输出:

$ go run cp3.go
usage: cp3 source destination BUFFERSIZE
$ go run cp3.go fileCP.txt /tmp/buf10 10
Copying fileCP.txt to /tmp/buf10
$ go run cp3.go fileCP.txt /tmp/buf20 20
Copying fileCP.txt to /tmp/buf20

在接下来的基准测试中,你会发现,缓冲区的大小极大地影响了 cp3.go 的性能。

运行基准测试

在本文的最后一部分,我将尝试比较这三个程序以及 cp3.go 在不同缓冲区大小下的性能(使用 time(1) 命令行工具)。

以下输出显示了复制 500MB 大小的文件时,cp1.gocp2.gocp3.go 的性能对比:

$ ls -l INPUT
-rw-r--r--  1 mtsouk  staff  512000000 Jun  5 09:39 INPUT
$ time go run cp1.go INPUT /tmp/cp1
Copied 512000000 bytes!

real    0m0.980s
user    0m0.219s
sys     0m0.719s
$ time go run cp2.go INPUT /tmp/cp2

real    0m1.139s
user    0m0.196s
sys     0m0.654s
$ time go run cp3.go INPUT /tmp/cp3 1000000
Copying INPUT to /tmp/cp3

real    0m1.025s
user    0m0.195s
sys     0m0.486s

我们可以看出,这三个程序的性能非常接近,这意味着 Go 标准库函数的实现非常聪明、经过了充分优化。

现在,让我们测试一下缓冲区大小对 cp3.go 的性能有什么影响吧!执行 cp3.go,并分别指定缓冲区大小为 10、20 和 1000 字节,在一台运行很快的机器上复制 500MB 文件,得到的结果如下:

$ ls -l INPUT
-rw-r--r--  1 mtsouk  staff  512000000 Jun  5 09:39 INPUT
$ time go run cp3.go INPUT /tmp/buf10 10
Copying INPUT to /tmp/buf10

real    6m39.721s
user    1m18.457s
sys 5m19.186s
$ time go run cp3.go INPUT /tmp/buf20 20
Copying INPUT to /tmp/buf20

real    3m20.819s
user    0m39.444s
sys 2m40.380s
$ time go run cp3.go INPUT /tmp/buf1000 1000
Copying INPUT to /tmp/buf1000

real    0m4.916s
user    0m1.001s
sys     0m3.986s

我们可以发现,缓冲区越大,cp3.go 运行得就越快,这或多或少是符合预期的。此外,使用小于 20 字节的缓冲区来复制大文件会非常缓慢,应该避免。

你可以在 GitHub 找到 cp1.gocp2.gocp3.go 的 Go 代码。

如果你有任何问题或反馈,请在(原文)下方发表评论或在 Twitter 上与我(原作者)联系。


via: https://opensource.com/article/18/6/copying-files-go

作者:Mihalis Tsoukalos 选题:lkxed 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

随着自动化扩展到涵盖 IT 的更多方面,越来越多的管理员正在学习自动化技能并应用它们来减轻他们的工作量。自动化可以减轻重复性任务的负担,并为基础设施增加一定程度的一致性。但是,当 IT 工作人员部署自动化时,会出现可能对大大小小的基础设施造成严重破坏的常见错误。在自动化部署中通常会出现五个常见错误。

缺乏测试

初学者常犯的错误是自动化脚本没有经过全面测试。由于拼写错误或逻辑错误,简单的 shell 脚本可能会对服务器产生不利影响。将该错误乘以基础架构中的服务器数量,你可能会遇到一大堆问题需要清理。在大规模部署之前始终测试你的自动化脚本。

意外负载

经常发生的第二个错误是没有预测脚本可能对其他资源施加的系统负载。当目标是十几个服务器时,运行从仓库下载文件或安装包的脚本可能没问题。脚本通常在成百上千台服务器上运行。这种负载可以使支持服务停止或完全崩溃。不要忘记考虑端点影响或设置合理的并发率。

离开脚本

自动化工具的一种用途是确保符合标准设置。自动化可以轻松确保组中的每台服务器都具有完全相同的设置。如果该组中的服务器需要根据该基线进行更改,同时管理员不了解合规标准,那么可能会出现问题。安装和启用不需要和不想要的服务,从而导致可能的安全问题。

缺乏文档

管理员的一项固定职责应该是记录他们的工作。由于合同到期、升职或定期员工流动,公司可能会在 IT 部门频繁招聘新员工。公司内的工作组相互隔离也很常见。由于这些原因,重要的是记录哪些自动化已经到位。与用户运行脚本不同,自动化可能会在创建它的人离开组之后继续很长时间。管理员可能会发现自己在其基础设施中面临着来自未经检查的自动化的奇怪行为。

缺乏经验

列表中的最后一个错误是管理员对他们正在自动化的系统不够了解。管理员经常被雇用到他们没有接受过足够培训且没有人可以求教的职位上工作。自 COVID 以来,当公司努力填补空缺时,这一点尤其重要。然后管理员被迫处理他们没有设置并且可能不完全理解的基础设施。这可能会导致非常低效的脚本浪费资源或配置错误的服务器。

结论

越来越多的管理员正在学习自动化来帮助他们完成日常任务。因此,自动化正被应用于更多的技术领域。希望此列表将有助于防止新用户犯这些错误,并敦促经验丰富的管理员重新评估他们的 IT 策略。自动化旨在减轻重复性任务的负担,而不是为最终用户带来更多工作。


via: https://fedoramagazine.org/five-common-mistakes-when-using-automation/

作者:Gary Scarborough 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Plex.tv 终于增加了 Linux 桌面版本和全新的 HTPC 应用。不过,它目前只提供了 Snap 包。

plex

Plex 是一个流行的流媒体播放器,同时,它能够用作一个媒体服务器软件。

事实上,它也是 Linux 上最好的媒体服务器软件 之一。

是的,这个媒体服务器已经支持 Linux,而且还提供了一个 包含安装步骤的教程

Linux 上的 Plex 桌面播放器提供 Snap 包

我知道很多人都不喜欢使用 Snap 包来安装这个桌面播放器。但现在,这个桌面播放器已在 Snap 商店中提供,你可以轻松地在任何 Linux 发行版上安装它。

幸运的是,这个桌面播放器的 公告 还提到他们正在开发一个 Flatpak 包,它应该会在近期登陆 Flathub。

这样一来,借助 Flatpak 和 Snap 软件包,Plex 就可以成为在 Linux 上流式传输和组织个人媒体收藏的绝佳选择。

除了桌面应用程序,如果你利用你的 Linux 机器连接到一个大屏幕来观看所有的内容,还有一个 Plex HTPC(有计划发布 Flatpak 软件包)。

顺便说一句,HTPC 是 PMP TV(全称为 Plex Media Player TV)模式的继承者。

他们在官网上与它的 Linux 桌面应用程序一同发布了这款产品。

使用 HTPC,这个桌面应用就可以和电视共享,并支持音频直通、刷新率切换、控制器和可配置输入映射等高级功能。

因此,如果你有一个大屏幕,并且想要连接你的系统(不管是什么桌面平台)的话,你现在可以使用 HTPC 应用程序来完成。

Plex 桌面版
Plex HTPC

在 Linux 系统或联网电视上流式传输内容时,你通常会使用什么呢?你觉得 Plex 能满足你的需求吗?即然它支持 Linux 了,你会想要用它来替代当前使用的软件吗?

欢迎在评论区告诉我们你的想法!


via: https://news.itsfoss.com/plex-desktop-linux/

作者:Ankush Das 选题:lkxed 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

首个性能超百亿亿次的超算夺得 TOP500 榜单第一

在刚刚发布的 第 59 期 TOP500 超算榜单 中,美国橡树岭国家实验室的 Frontier 超算超越了过去两年的榜首日本富岳超算,成为全球超算冠军。它是该榜单首个速度超过百亿亿次的超算,成为了第一台真正的超大规模计算机。不仅如此,Frontier 也同时取得了 GREEN500 和 HPL-AI 榜单第一名。Frontier 使用的是 AMD 的 CPU 及图形加速器,榜单前十名中共有五台超算使用的是 AMD 的处理器。中国此次参加排名的最快超算还是之前的神威·太湖之光,排名第六,列入 TOP500 的中国超算共有 173 个。而之前被传的中国突破百亿亿次的两台超算都没参与 TOP500 榜单排名。

消息来源:wccftech
老王点评:计算技术突飞猛进啊,这次美国算是露出一点家底了。

systemd 带来实验性的 A/B 式镜像更新功能

ChromeOS 有两个根分区:一个使用的,一个备用的。当前运行的操作系统更新备用分区,然后你重新启动到备用分区。如果一切正常,它就会更新现在闲置的原来的分区。如果它不能完美地工作,那么你仍然有以前的版本可以使用,你可以再次重启回去。而最新发布的 Systemd 初始化系统带来了一个实验性的功能,提供了类似 ChromeOS、Fedora Silverblue 的这种 A/B 式镜像更新功能。Systemd 的创始人 Lennart Poettering 说:“让我们普及基于镜像的操作系统,现代化的安全属性围绕着不变性、安全启动、TPM2、适应性、自动更新、出厂重置、统一性而建立,由传统的发行包建立,但通过镜像部署。”

消息来源:theregister
老王点评:虽然很多人反对 systemd 的这种二进制、大一统和复杂的结构,但公平的讲,systemd 的很多理念很先进,其最新带来的这种 A/B 式更新功能也颇有价值。ChromeOS 证明了对于普通用户来说,就是让他们尽量少的接触底层细节,出现问题后整个回滚是最好的。但是,Linux 还是那个 Linux 吗?或许是我太守旧了。

勒索软件攻击让美国一个县回到纸质时代

美国新泽西州萨默塞特县上周遭到勒索软件攻击,使得该县政府各部门的电子邮件服务瘫痪,使得“无法提供大多数依赖互联网访问的服务”,诸如土地记录、生命统计和遗嘱记录等只有 1977 年以前的纸质记录可供查找。

消息来源:theregister
老王点评:目前去各个公共部门办事,即便已经通过了电子审批,还是索要各种纸质文件,这样看起来也不是完全没有意义。: D

Tails 5.1 将针对“可绕过 Tor 浏览器安全措施的危险漏洞”提供关键修复。以下是它的全部内容。

Tails

Tails 是一个专注于安全的便携式 Linux 发行版,最近,它的开发团队发布了有关其当前版本的重要公告。他们警告用户在 Tails 5.0 或更早版本 上使用 Tor 浏览器时,避免输入或使用任何个人或敏感信息。

Tor 浏览器是 Tails 事实上的(默认)网页浏览器,它有助于在用户连接到互联网时,保护他们的在线身份。它主要被各种记者和活动家用来逃避审查。不过,普通用户也可以使用它。

问题说明

最近,有人发现了两个令人讨厌的漏洞,它们允许有害网站能够从其他网站窃取用户的信息。

这些都是在 Firefox 使用的 JavaScript 引擎中发现的。

但是,Tor 与此有什么关系?对于那些不知道的人来说,Tor 实际上是 Firefox 的一个复刻,因此包含许多类似的功能,如 JavaScript 引擎。

具体来说,在 Mozilla 发布的公告 中,这些漏洞已被确定为 CVE-2022-1802 和 CVE-2022-1529。

Tails 公告中也对此进行了说明:

“例如,在你访问恶意网站后,控制该网站的攻击者可能会在同一个 Tails 会话期间,访问你随后发送到其他网站的密码或其他敏感信息。”

你应该停止使用 Tail 发行版吗?

没有这个必要。

用户会很高兴地知道,这些漏洞并不影响 Tor 的连接。这意味着,如果你不交换任何敏感信息,如密码、个人信息、信息等,你可以随意地浏览互联网。

Tails 中的其他应用程序,尤其是 Thunderbird,仍然可以安全使用,因为 JavaScript 在使用时会被禁用。

此外,你也可以在 Tor 浏览器中启用最高的安全级别。这是推荐的,因为(该级别下)JavaScript 引擎会被禁用。不过,请注意,这会使网站无法正常运行。

换句话说,如果你知道自己在做什么的话,Tails 发行版仍然可以安全使用。

漏洞修复即将发布

好的消息是,Mozilla 已经在上游修补了这些错误,现在就等 Tails 团队发布修复程序了。

至于何时发布,他们是这样说的:

此漏洞将在 Tails 5.1(5 月 31 日)中修复,但我们的团队没有能力提前发布紧急版本。

因此,你最好的选择是等待下周的 Tails 5.1 发布。你可以阅读 Tails 开发团队的 官方公告 以了解更多信息。


via: https://news.itsfoss.com/tails-tor-browser/

作者:Rishabh Moharir 选题:lkxed 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

带你一窥生成二进制文件步骤的幕后,以便在出现一些错误时,你知道如何逐步解决问题。

C 语言广为人知,深受新老程序员的好评。使用 C 语言编写的源文件代码,使用了标准的英语术语,因而人们可以方便阅读。然而,计算机只能理解二进制代码。为将代码转换为机器语言,你需要使用一种被称为 编译器 compiler 的工具。

最常见的编译器是 GCC( GNU 编译器集 GNU Compiler Collection )。编译过程涉及到一系列的中间步骤及相关工具。

安装 GCC

为验证在你的系统上是否已经安装了 GCC,使用 gcc 命令:

$ gcc --version

如有必要,使用你的软件包管理器来安装 GCC。在基于 Fedora 的系统上,使用 dnf

$ sudo dnf install gcc libgcc

在基于 Debian 的系统上,使用 apt

$ sudo apt install build-essential

在安装后,如果你想查看 GCC 的安装位置,那么使用:

$ whereis gcc

演示使用 GCC 来编译一个简单的 C 程序

这里有一个简单的 C 程序,用于演示如何使用 GCC 来编译。打开你最喜欢的文本编辑器,并在其中粘贴这段代码:

// hellogcc.c
#include <stdio.h>

int main() {
    printf("Hello, GCC!\n");
    return 0;
}

保存文件为 hellogcc.c ,接下来编译它:

$ ls
hellogcc.c

$ gcc hellogcc.c

$ ls -1
a.out
hellogcc.c

如你所见,a.out 是编译后默认生成的二进制文件。为查看你所新编译的应用程序的输出,只需要运行它,就像你运行任意本地二进制文件一样:

$ ./a.out
Hello, GCC!

命名输出的文件

文件名称 a.out 是非常莫名其妙的,所以,如果你想具体指定可执行文件的名称,你可以使用 -o 选项:

(LCTT 译注:注意这和最近 Linux 内核废弃的 a.out 格式无关,只是名字相同,这里生成的 a.out 是 ELF 格式的 —— 也不知道谁给起了个 a.out 这破名字,在我看来,默认输出文件名就应该是去掉了 .c 扩展名后的名字。by wxy)

$ gcc -o hellogcc hellogcc.c

$ ls
a.out hellogcc hellogcc.c

$ ./hellogcc
Hello, GCC!

当开发一个需要编译多个 C 源文件文件的大型应用程序时,这种选项是很有用的。

在 GCC 编译中的中间步骤

编译实际上有四个步骤,即使在简单的用例中 GCC 自动执行了这些步骤。

  1. 预处理 Pre-Processing :GNU 的 C 预处理器(cpp)解析头文件(#include 语句),展开 macros 定义(#define 语句),并使用展开的源文件代码来生成一个中间文件,如 hellogcc.i
  2. 编译 Compilation :在这个期间中,编译器将预处理的源文件代码转换为指定 CPU 架构的汇编代码。由此生成是汇编文件使用一个 .s 扩展名来命名,如在这个示例中的 hellogcc.s
  3. 汇编 Assembly :汇编程序(as)将汇编代码转换为目标机器代码,放在目标文件中,例如 hellogcc.o
  4. 链接 Linking :链接器(ld)将目标代码和库代码链接起来生成一个可执行文件,例如 hellogcc

在运行 GCC 时,可以使用 -v 选项来查看每一步的细节:

$ gcc -v -o hellogcc hellogcc.c

Compiler flowchart

手动编译代码

体验编译的每个步骤可能是很有用的,因此在一些情况下,你不需要 GCC 完成所有的步骤。

首先,除源文件文件以外,删除在当前文件夹下生成的文件。

$ rm a.out hellogcc.o

$ ls
hellogcc.c

预处理器

首先,启动预处理器,将其输出重定向为 hellogcc.i

$ cpp hellogcc.c > hellogcc.i

$ ls
hellogcc.c hellogcc.i

查看输出文件,并注意一下预处理器是如何包含头文件和扩展宏中的源文件代码的。

编译器

现在,你可以编译代码为汇编代码。使用 -S 选项来设置 GCC 只生成汇编代码:

$ gcc -S hellogcc.i

$ ls
hellogcc.c hellogcc.i hellogcc.s

$ cat hellogcc.s

查看汇编代码,来看看生成了什么。

汇编

使用你刚刚所生成的汇编代码来创建一个目标文件:

$ as -o hellogcc.o hellogcc.s

$ ls
hellogcc.c hellogcc.i hellogcc.o hellogcc.s

链接

要生成一个可执行文件,你必须将对象文件链接到它所依赖的库。这并不像前面的步骤那么简单,但它却是有教育意义的:

$ ld -o hellogcc hellogcc.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: hellogcc.o: in function `main`:
hellogcc.c:(.text+0xa): undefined reference to `puts'

在链接器查找完 libc.so 库后,出现一个引用 undefined puts 错误。你必须找出适合的链接器选项来链接必要的库以解决这个问题。这不是一个小技巧,它取决于你的系统的布局。

在链接时,你必须链接代码到 核心运行时 core runtime (CRT)目标,这是一组帮助二进制可执行文件启动的子例程。链接器也需要知道在哪里可以找到重要的系统库,包括 libclibgcc,尤其是其中的特殊的开始和结束指令。这些指令可以通过 --start-group--end-group 选项来分隔,或者使用指向 crtbegin.ocrtend.o 的路径。

这个示例使用了 RHEL 8 上的路径,因此你可能需要依据你的系统调整路径。

$ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
    -o hello \
    /usr/lib64/crt1.o /usr/lib64/crti.o \
    --start-group \
        -L/usr/lib/gcc/x86_64-redhat-linux/8 \
        -L/usr/lib64 -L/lib64 hello.o \
        -lgcc \
        --as-needed -lgcc_s \
        --no-as-needed -lc -lgcc \
    --end-group \
    /usr/lib64/crtn.o

在 Slackware 上,同样的链接过程会使用一组不同的路径,但是,你可以看到这其中的相似之处:

$ ld -static -o hello \
    -L/usr/lib64/gcc/x86_64-slackware-linux/11.2.0/ \
    /usr/lib64/crt1.o /usr/lib64/crti.o hello.o /usr/lib64/crtn.o \
    --start-group \
        -lc -lgcc -lgcc_eh \
    --end-group

现在,运行由此生成的可执行文件:

$ ./hello
Hello, GCC!

一些有用的实用程序

下面是一些帮助检查文件类型、 符号表 symbol tables 和链接到可执行文件的库的实用程序。

使用 file 实用程序可以确定文件的类型:

$ file hellogcc.c
hellogcc.c: C source, ASCII text

$ file hellogcc.o
hellogcc.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

$ file hellogcc
hellogcc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb76b241d7d00871806e9fa5e814fee276d5bd1a, for GNU/Linux 3.2.0, not stripped

对目标文件使用 nm 实用程序可以列出 符号表 symbol tables

$ nm hellogcc.o
0000000000000000 T main
             U puts

使用 ldd 实用程序来列出动态链接库:

$ ldd hellogcc
linux-vdso.so.1 (0x00007ffe3bdd7000)
libc.so.6 => /lib64/libc.so.6 (0x00007f223395e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2233b7e000)

总结

在这篇文章中,你了解到了 GCC 编译中的各种中间步骤,和检查文件类型、 符号表 symbol tables 和链接到可执行文件的库的实用程序。在你下次使用 GCC 时,你将会明白它为你生成一个二进制文件所要做的步骤,并且当出现一些错误时,你会知道如何逐步处理解决问题。


via: https://opensource.com/article/22/5/gnu-c-compiler

作者:Jayashree Huttanagoudar 选题:lkxed 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出