惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
Cloudbric
Cloudbric
云风的 BLOG
云风的 BLOG
A
About on SuperTechFans
The GitHub Blog
The GitHub Blog
IT之家
IT之家
F
Full Disclosure
B
Blog RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
B
Blog
H
Help Net Security
The Cloudflare Blog
Recorded Future
Recorded Future
P
Proofpoint News Feed
P
Proofpoint News Feed
C
Cisco Blogs
T
Tailwind CSS Blog
P
Palo Alto Networks Blog
D
Docker
爱范儿
爱范儿
Know Your Adversary
Know Your Adversary
博客园 - 聂微东
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Y
Y Combinator Blog
雷峰网
雷峰网
AWS News Blog
AWS News Blog
D
DataBreaches.Net
博客园 - 司徒正美
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - Franky
C
Cybersecurity and Infrastructure Security Agency CISA
Blog — PlanetScale
Blog — PlanetScale
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
MongoDB | Blog
MongoDB | Blog
C
CERT Recently Published Vulnerability Notes
阮一峰的网络日志
阮一峰的网络日志
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
CXSECURITY Database RSS Feed - CXSecurity.com
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
腾讯CDC
小众软件
小众软件
G
Google Developers Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Scott Helme
Scott Helme
O
OpenAI News

机核

游戏性能旗舰最强之选,一加 Ace 6 至尊版国补到手价2999元起 6元钱自己更换电动车刹车线 《生化危机9:安魂曲》编剧Haris Orkin专访 摸金游戏?音乐游戏!暗区新赛季这把能弹的琴有何来历? 好评国产武侠SRPG《息风谷战略》免费DLC现已推出 | 机核 GCORES 碎片 《生化危机:安魂曲》将于5月实装首个免费更新内容 | 机核 GCORES 新锐东方游戏,谱写世界新章! 沉浸式恋爱视觉小说游戏《心象演算》免费试玩版现已正式上线 | 机核 GCORES 互动影游《代号三国:龙起》上线!穿越三国与曹操并肩、与佳人同行、与权谋博弈! 《老头收集梦想生活》,游戏酒桌会6,录音笔VOL.689 | 机核 GCORES 破界·共生——《白日梦:无限世界》五大核心游戏特点解析 价格已到史低,锐龙5 9600X/锐龙7 9700X正适合抄底 时间循环之旅即刻启程!《归环》一周目测试今日开启 热门在线韩游变魂游,洛奇衍生作能否打破“花瓶”魔咒? LG UltraGear evo 全新高端显示器系列:当“5K”遇见“AI”,不止强大,更懂热爱 反套路三国互动影游《代号三国:龙起》今日上线! 愿望单登记人数突破10万!备受瞩目的“女儿养成游戏” 《梦幻魔法公主》今日于Steam平台上线!限时八折优惠中 《黑神话:悟空》全球音乐会2026巡演将于4月29日12时开票 | 机核 GCORES “Snowguelike”生存肉鸽挖矿新作《蛙穿雪境》公布发售日期,5月7日正式上线 | 机核 GCORES 重塑移动办公、AI创作新境!全新华硕灵耀Air系列、ProArt 骁龙版震撼首发,创芯未来 均分88:《Saros》媒体评分汇总 | 机核 GCORES 上海烛龙公布合作遗迹探险游戏《吉时已到》首支预告片 | 机核 GCORES 首个独立游戏《萝薇日记》已上线Steam! 烛龙新IP《吉时已到》首曝,打造国内首款中式合作遗迹探险游戏 喜加一:《暗黑破坏神Ⅳ》国服现已开启限时免费领取本体活动 | 机核 GCORES 新版《生化危机》电影定于9月18日上映,官方网站现已上线 | 机核 GCORES 《冲就完事模拟器2》“星球大战”联动DLC正式宣布 | 机核 GCORES SteamController将于5月4日发售,售价99美元 | 机核 GCORES 事已至此,内存这么用也算省钱了,“2+1”非对称双通道应用实测 505游戏母公司现已收购《明末:渊虚之羽》IP | 机核 GCORES 基石 手动杂谈12|格斗游戏也能讲好故事 | 机核 GCORES I Love You Mr Snowball 我的向日葵小姐 会比GTA6先发售吗?最硬核的生存游戏《DAYZ》要推新DLC了 周记02:在2026遇到新怪谈 什么硬件,能让游戏Loading界面快速消失? | 机核 GCORES GadioSpec《百年风云世界杯》免费试听集 | 机核 GCORES 百年风云世界杯Vol.1丨足球崛起 | 机核 GCORES 100年前,一群人提前替我们经历了AI恐惧 | 机核 GCORES 《呼啸山庄》2026 ——一辆当代艺术的大卡车冲撞了我的大脑 手游《天穗之咲稻姬:日之香巡灵传》宣布将于7月27日停服 | 机核 GCORES 集结梦之队,征战世界杯,《最佳球会ONLINE》上线Steam 山水绝景随心拼 休闲建造游戏《千里山河录》Steam商店页公开 巫师帽、法袍、魔杖,为什么它们是影视、游戏里的法师必备三件套? 《无鞘信使》-第一章 复古风自动战斗肉鸽《终结之终结》Steam商店页面现已上线 电脑里有一款不破不立的MMO,录音笔VOL.688 | 机核 GCORES 《生化危机》30周年纪念周边发售,这次是真的“保护伞” 可靠耐用+AI全能,惠普战66 2025锐龙版深度体验 《时之书:无尽终章》关卡“大航海时代”全球首次公开 经典名作《乌龙派出所》改编经营模拟游戏《乌龙派出所~阿两的商店街物语~》正式宣布支持简体中文 明日开冲,解锁反套路三国互动影游《代号三国:龙起》即将上线 独立游戏《这是我的宝藏!》已发售~ 降低难度不是唯一解,无压力死亡也是好体验 Netflix官宣新片《普通人》:讲述韩国现代史上的权力风暴 《生化危机:安魂曲》全球销量现已突破700万份 《绝地鸭卫》PC版5月15日正式发售 亡妻回忆录?女性向情感叙事游戏《S-mail》现已正式发售 全新酷黑风格,酷睿Ultra 200S PLUS的高性价比搭档!七彩虹BATTLE-AX B860M-PLUS S WIFI7 V20 超级黑刃主板测评 胖狗 索尼发布了港台地区PS5产品价格调整公告,将于5月1日起实施 二次元怪猎+性感美女!《碧蓝幻想:无尽黄昏》开启Beta公测 这款怪谈类型中式恐怖游戏居然更新了?! 《百日战纪 -最终防卫学园-》改编漫画将于今年冬季开启连载 《如被附身,请致电我们》:匈牙利黑色幽默恐怖小说 《竹屿山房杂部卷五》(译文) 大树 超越引擎 摄影分享丨四月 碎片杂记vol.72 当老式FPS与老式动画技术碰撞出新时代的火花—《神探杰克鼠》 想做独游,如何避免首个项目就褒姒? 在线多人动作游戏《OCTOPinbs》将于5月12日上线Steam! 《酒鬼女神的酒诡》确定将于2026年登陆Steam 《CRYMELIGHT》将于11月5日(周四)正式发售!4月25日开始预购! 钢铁国度MKI部落Evolution,蛮兵部分 【钢铁国度】部落-Primal MK I,蛮兵部分 这是一个高中生用ai跑出来的作品,我自称他为物理神话 钢铁国度MKI部落Metamorphosis,蛮兵部分上 钢铁国度MKI部落Metamorphosis,蛮兵部分下 INDIE Live Expo于4月25日举办:首发9款新作,超200款独立游戏亮相 暗黑卡牌策略新作《魔忌:穷鼠啮狸》发布全新中文试玩版和发售预告片,4月30日正式上线在即 战锤40K长篇小说:变节者・苦难主宰(三)(全书完) 战锤40K长篇小说:变节者・苦难主宰(二) 战锤40K长篇小说:变节者・苦难主宰(一) 英语语言学习丨短语专题1:短语的特点 唯一的EVE战争 【少前同人】【M200】战术人形会梦见音乐会吗 【昏迷3】即将发售,前作主角悉数到场!“恶灵”宋老师化身可操控角色 业内人士:游戏公司“十有八九”使用生成式AI,包括卡普空 欢庆一周年:《光与影:33号远征队》全新纪念艺术图、超值折扣与免费更新同步上线 才刚刚开始呢 【抽奖】《星际卡车司机》推出免费大型更新,四折平史低折扣进行中 四人合作FPS游戏《佣兵猎手》抢先体验重大更新 1 现已上线 Raw Fury新作《深馅地牢 Deep Dish Dungeon》将于今秋加入 XGP 塔防幸存者游戏《魔怪来袭》推出首个 DLC 《饿狼传说:群狼之城》1周年纪念!新DLC“沃尔夫冈·克劳萨”今日参战 猫狗相伴 欢乐闯关 双人合作平台跳跃游戏《猫狗同行》上线Steam商店页 《同行:月球逃脱》(Together: Moon Escape)上线将于明日上线Steam
C:从 “hello world” 程序开始
Thopterist · 2024-12-20 · via 机核

一、前言

最近在机核网看到了不少编程相关的内容,故也贡献一篇拙作,“娱乐为主”。文中若有错误,还烦请读者在评论区不吝赐教。文中有的没有说明的专有名词使用加粗格式,读者可以自行查找它们的定义。

免责声明:尽管本文的行文看起来很适合新手入门,但事实上涉及了不少前置知识,属于胡侃的文章。如果有学习计算机语言的想法,请移步站内站外其他的文档书籍,以及寻求专业人士的建议。

免责声明2:由于笔者本人此时不在国内,文中的有些链接没有验证国内网络是否可以正常打开。

二、什么是 hello world ?

hello world 程序是计算机编程语言中广泛存在的最简单的示例程序。一般来说,这样的程序仅在屏幕上输出一个简单的句子,即“hello, world”。它常常作为每个计算机语言学习的第一课,展示语言最基本的特性。某种程度上,这样的程序能够反映语言的特性,例如年龄大于家父的 COBOL,它的示例程序是这样的:

[hello.cbl]

000100 IDENTIFICATION DIVISION.

000200 PROGRAM-ID. HELLO.

000300 AUTHOR. JOE PROGRAMMER.

000400 ENVIRONMENT DIVISION.

000500 DATA DIVISION.

000600 PROCEDURE DIVISION.

000700 MAINLINE.

000800 DISPLAY 'Hello World!'.

000900 STOP RUN.

(COBOL今日仍然作为一个活跃的编程语言存在于金融领域,“代码很稳定,便不要动它”。)

而今天越来越招人喜欢的 Python,它的 hello world 程序则是:

print("hello, world")

回到我们的主题,一般而言,C 的 hello world 程序如下:

#include <stdio.h>

int main(void) {

printf("Hello World!\n");

return 0;

}

接下来,我们将逐行分析这个程序。

三、程序之外

虽然上一张的最后一句提到逐行分析这个程序,但是分析程序之前,不得不提一提 C 语言的编译。一般而言,计算机语言分为编译型语言和解释型语言。编译型语言编译器(compiler)将代码编译为二进制程序,代码的逻辑可以通过执行编译的二进制程序实现;解释型语言则将代码读入写好的解释器(interpreter),代码的逻辑通过解释器的行为解读。

C 作为一门编译型语言,需要将代码写在一个形如 hello.c 这样的文件中,并且通过一个编译器进行编译操作。编译的结果往往是一个名为 hello(无后缀)或者 hello.exe 的程序。程序的名字和操作系统、编译器、编译时的选项相关。在 Ubuntu 20.04 LTS 中,执行如下命令将会生成一个可执行的二进制文件 a.out 。

cc hello.c

执行这一程序(一般情况下)将会打印出“Hello World!”的字符,并且将0作为返回值返回,代表程序正常地完成了执行。

注意到编译器和语言之间并不是游戏引擎和项目文件那样一个一对一的关系。一门编程语言的设计者不一定需要写出一个特别的编译器,只需要指定语言的语法标准即可。编译器可以是由完全无关的全体或个人遵守语言的语法标准实现的。今天,最常用的 C 语言编译器有 GCC、Clang 等等。有的网站可以在线编译程序,应要求生成不同版本的汇编代码。如 Compiler Explorer 。另外,微软处于某种考量使得在 Windows 系统上编译 C 程序的流程相当复杂(官方推荐的方法好像是使用 Visual Studio 全家桶)。

四、第一行代码

第一行代码为

#include <stdio.h>

其中,# 代表它是一行“预处理器指令”,按照微软文档的说明,

预处理器是将源文件的文本作为翻译的第一阶段操作的文本处理器。 预处理器不会分析源文本,但会将源文本细分为标记来查找宏调用。 尽管编译器一般会在其第一个传递中调用预处理器,但还是可以为了在不进行编译的情况下处理文本而单独调用预处理器。

“#include”是常用的预处理器指令之一,它的作用是引入外部头文件(header file)的定义。被引入的文件名会使用一对尖括号或者一对 dumb quotes 包裹。所谓的 dumb quotes 即不分左右的引号,在常见的中文键盘(QWERTY——以键盘左上角的键命名)中,需要将输入法调整至英文模式,并且按下回车键左边那个按键来输入它。它与中文输入法常见的左右引号是不同的;后者不能构成有效的 include 指令,并且在有的文件编码下可能不能正确地保存。

在 include 指令中使用尖括号或 dumb quotes 会影响编译器寻找头文件的顺序。一般而言,对于标准库,或者使用包管理器安装的库文件,使用尖括号;对于本地的头文件,使用 dumb quotes 是适宜的行为。“#include”语句后没有分号。多个“include”语句之间的顺序有时是重要的。像 clang-format 这样的程序通过设置会按一定的顺序重新排列语句的顺序。相关的讨论参照 StackOverflow

像 include 指令这样的预处理器指令对于绝大多数编译器来说是定义好的,有的预处理器指令则未必在所有的编译器中有定义——换言之,它们是“非标准”的,可能没有在一些编译器中实现。常见的非标准的预处理器命令如 “#pragma once”。在头文件中使用这一命令可以保证一个头文件的定义仅会被引入一次——忘记对头文件进行类似的处理并多次导入同一头文件会导致错误。这一操作也可以使用 ifndef-define-endif 操作实现,被称作 include guard

在本行代码(#include <stdio.h>)中,include 引入的头文件是 stdio.h 。std 是 standard 的缩写,代表该文件是一个标准库的头文件。io 是 input-output 的缩写,代表着该标准库处理的是输入-输出的问题。在这一库中定义了 printf 这一函数,我们在示例程序的第三行使用了这一函数。虽然,printf 仅仅是这一库中定义的诸多函数之一;该库中常见的函数还有 puts, scanf 等等。有的网站,如 die.net 可以找到该库文件的文档。在 Debian 一类的系统中,这一库的文档应该在已经安装在了系统中,可以通过 man page 找到。

五、第二行代码

第二行代码为

int main(void) {

该行代码同后面几行的代码共同构成了对 main 函数的定义。这里需要区分函数声明(declaration)同函数定义(definition)之间的区别。函数声明定义所谓的函数原型,对函数体则不作定义。此处出现的则是函数定义则定义了函数体的内容。二者的区别,通俗来说,是“做什么”和“怎么做”的区别。假如 main 函数有一个声明,则它的样子(可以)是这样的:

int main(void); // 这样

int main(); // 或是这样

声明以分号结尾而非大括号,语义上是完整的。它表明存在一个叫做 main 的函数,它没有输入的参数(因此括号内是“void”或空,二者没有区别),并且输出一个整数类型的变量(也就是前面的 int 即英文 integer 的缩写)。

main 函数是 C 语言的特殊函数,是它的 entry point 。在一个可运行的 C 程序源文件(复数)中通常应当有且只有一个 main 函数的定义。程序会从 main 函数的定义处开始执行。此函数可以带有零个或两个参数。根据维基百科,有的系统中可以有三个或者四个参数,它们依次定义如下:

int main(void) // 0 int main(int argc, char *argv[]) // 2

int main(int argc, char *argv[], char *envp[]) // 类 unix

int main(int argc, char *argv[], char *envp[], char *apple[]) // 苹果系统

对于带有两个参数的版本,第一个参数 int argc 代表函数接受一个整数类型的变量,在函数内以 argc 的名字作为变量存在,第二个参数 char *argv[] 代表函数接受一组变量,其中的每一个变量的类型是 char * ,是一个指向 char 类型的指针。指向 char 类型的指针在 C 中往往作为字符串类型使用。此处 argc 为 argument count 的缩写,而 argv 则为 argument value 的缩写。两个参数的 main 函数用通俗语言解释的话,指——“声明一个含有两个参数的 main 函数;第一个参数为整数类型,它的名字是 argc,为程序执行时参数的数量;第二个参数为字符串数组,它的名字时 argv,代表程序执行时给予的具体的每一个参数;该函数将会返回一个整数值作为函数的输出”。

只了解 Python 这样更加现代的语言的读者可能不理解为什么需要一个叫做 argc 的参数来给出函数参数的数量。这是因为 C 语言的数组(方括号的语义)并不同于后来者,它的长度并没有一个方法去查询,因此得靠 argc 来给出参数的数量。

另外,之前提到的 main 函数也可以被声明为以下的形式:

int main(int argc, char** argv);

int main(int argc, char const **argv);

int main(int argc, char const *const *argv);

int main(int argc, char const *const *const argv);

原因在于 argv 作为函数参数在编译时不知道大小,且本质上只是指向数组第一项的指针。至于 const 修饰符则用于告诉编译器此处 argv 的值或它指向的值不应该被修改(一般的程序也不应当修改),属于额外的限制,对于 hello world 这一程序而言没有区别。

回到本行代码,int main() 后端的左花括号同第五行的右花括号对应。两者之间的代码(也就是示例的第四行和第五行)描述了 main 函数“作什么”的函数定义。花括号在 C 语言中大致可以理解为隔间,里面的东西被作为一个整体来考虑。考虑以下例子:

if (1) return 0;

if (1) {

return 0;

}

二者的语义是相同的,只不过后者中返回语句被包裹在了一对括号里。这样的分隔有时候是必须的,否则会导致语义不清的问题,如同那个笑话“粮食不卖给共产党”——究竟是“不卖给共产党”,还是“不卖,给共产党”,不加分隔的句子会有歧义。对于伪代码

if P if Q else ...

因为缺乏分隔的方法,就说不清楚 else 后面的内容究竟是对应第一个 if 还是第二个 if。

另外,花括号实际上定义了一个作用域(scope)。对于 C 语言而言,变量的定义是同它的作用域相关的。假如我们修改之前的例子

// 本程序无法编译

#include <stdio.h>

void f() {

a += 1; // 删掉这行则可以编译

}

int main(void) {

int a = printf("Hello World!\n");

f();

return 0;

}

这样的例子是无法通过编译器编译的,因为虽然在 main 中定义了一个整型变量 a,该变量在函数 f 中则未定义,因此编译器“不确定”在函数 f 中出现的 a 的定义,导致编译失败。于此同时,假如将 a 定义为全局变量,则在两个函数中都能找到它的定义,也就不会出现这个编译错误。(随便使用全局变量会导致更多的麻烦,不建议需要这条建议的人随便声明全局变量。)

// 修改后的版本,可以编译

#include <stdio.h>

int a;

void foo() {

a += 1;

}

int main(void) {

a = printf("Hello World!\n");

foo();

return 0;

}

六、第三行代码

第三行代码是

printf("Hello World!\n");

此处我使用了四个空格的缩进来区别第二行到第五行中被花括号包裹的内容。使用缩进来区分不同的代码块是编程中的标准做法,但是对于缩进的具体形式有许多的分歧。有两个空格、四个空格、一个制表符的做法。对 C 语言来说所有的做法都是可以的,编译器并不会理会多余的空格和制表符;缩进只是方便编程者阅读编写代码。这一点同 Python 不同,后者的空格缩进是划分作用域的工具,因此不能够随意添加或删除。

因为 C 中一个“空”的语句是允许的,用分号来缩进也是可以的:

// 依旧可以编译

#include <stdio.h>

int main(void) {

;;;;printf("Hello World!\n");

;;;;return 0;

}

这一行代码调用了 printf 函数(在 include <stdio.h> 中定义——参见第一行的解说),“printf” 意为 print-format,打印的是格式化字符串(format string)。在 hello world 的例子中,格式化字符串没有真正展现出它的特点,因此这一行代码和如下的代码是等价的(注意字符串有区别)。

puts("Hello World");

再举一个格式化字符串有用的例子,借用一个上古笑话

#include <stdio.h>

int main(void) {

int a = 25;

printf("OCT %o = DEC %d\n", a, a);

}

此处 printf 的参数有三个,第一个是一个格式化字符串 "OCT %o = DEC %d \n",第二个和第三个为之前申明的整型变量 a,值为25。printf 通过分析第一个参数找到了其中的两个占位符 %o 和 %d,将它们的值依次替换为了第二个参数和第三个参数的值(25和25)。其中第二个占位符指定为八进制(Octal),第三个指定为十进制(Decimal)。此外,特殊字符“\n”中的 n 意为 newline,为换行符,在终端打印的效果为不显示字符并且换行。因此最后打印的字符串是“OCT 31 = DEC 25”,新起一行。

此外,printf 函数有一个返回值,代表打印的字符数数量。此处没有使用它的返回值。

使用 printf 在屏幕上打印字符时,有时候字符不会在屏幕上立即显示,这是因为打印的字符会进入缓冲区(buffer)而非直接输出。这一选择是出于优化和效率的考虑。如果需要立即将缓冲区的内容输出,则需要调用另一个函数 fflush 。第一个 f 代表 file ,后面的 flush 可以理解为“冲水”。这里,具体的写法是

fflush(stdout);

这里的 stdout 对应的是 standard output ,逐字翻译的话是“标准-输出”。类似的还有 stdin ,即“标准-输入”,stderr ,即“标准-错误”,一般是用来显示报错信息。

在 C++ 中,使用 std::endl 进行换行的时候,也会清空缓冲区。

七,第四行代码

第四行代码是

return 0;

作用是标记函数的结束并且将0作为返回值返回,对应了函数声明时 int main ... 中的 int 。0 代表函数的执行正常完成。需要注意的是使用 return 会结束当前函数的执行,因此如果同一函数之后仍然有代码则它们不会执行。有的编程规范会限制一个函数中使用的 return 的数量。

对于 hello world 这个例子,本行代码是可以删除的。因为 main 函数的存在很寻常且返回 0 的情况也很寻常,如果省略本行代码编译器会自动将0作为返回值。

main 函数的返回值会作为程序执行的返回值;它常被用来判断是否程序正常地完成了运行。在 Linux 系统中,可以通过 $? 获得上一个命令执行的返回值。

./hello // 执行程序

echo $? // 获取返回值

八、结语

写这篇文章的主要原因是想写点什么而又找不到什么特别好的主题,所以从熟悉的领域入手,写了一篇娱乐为主的文章。正如我在前言里面说的,它的目的实在不是作教程——事实上它同我教 C 语言的想法正相反,包含了多余的琐碎的知识点,十分不适合新手建立一个关于 C 语言的一般印象。尽管如此,对于对这一编程语言已经有了一定了解的读者,我希望本文能在年末为诸位提供一点小小的娱乐。