现实如何且整套自然语言编译器的完整C源程序到底是什么样的??????

  • 报道称苹果新 iPhone 将着重升级影像系统,推出摄影版“人像模式”
  • 消息称 SpaceX 探索太空广告业务,未来将用以太币等加密货币购买卫星广告牌
  • 苹果回应隐私争议:检测儿童不良图片不会留下后门,且只在美国提供
  • 拆解发现华为 P50 Pro 采用三层 CPU:或因买不到麒麟 9000 专用内存
  • Facebook收购Giphy或涉嫌垄断,英国监管机构正在调查中
  • “假黄仁勋”刷屏之后,英伟达官方辟谣:只有14秒是虚拟的
  • OpenAI开启 Codex测试,一个将自然语言翻译成代码的 AI 系统

报道称苹果新 iPhone 将着重升级影像系统,推出摄影版“人像模式”

根据媒体周二援引知情人士报道,除了一系列常规升级外,今年苹果将着重升级影像系统,带来至少三项拍照和摄像方面的重大升级。

报道称,新款机型的摄像功能将引入此前只有拍照功能才能使用的“人像模式”,即对人物身后的背景进行虚化。与拍照模式一样,这一效果将通过深度传感器实现,用户可以在完成摄像后对模糊程度进行调整。

此外,新 iPhone 还会引入高质量视频存储格式 ProRes,给后期编辑更大的操作空间。与去年引入的 ProRAW 照片格式一样,这一功能可能将继续由 Pro 版本机型独占。此外爆料人表示苹果的照片编辑功能也将发生改变,与目前将滤镜应用在整张照片不同,新款机型将借助 AI 技术允许用户在白平衡不变的情况下调整颜色和高光。此外今年新机型的拍照将呈现一种更为平衡的风格,以更明亮的表现显示阴影和贴近现实的色彩。

考虑到苹果通常会隔年推出大型的功能变动,在去年引入 5G、调整外观和产品线后,今年的升级会相对温和。除了影像系统外,新手机还将按例升级 A15 芯片,减少屏幕“刘海”的面积,并使用新的屏幕技术提供高刷功能。

虽然目前苹果公司尚未确认发布新手机的时间和形式,考虑苹果已经宣布延后员工返回办公室的日程表,今年的秋季新品发布会有很大概率仍将以视频会议模式举行。

除了新手机外,知情人士表示公司在未来几个月里还有多条产品线将进行升级。其中包括使用自研芯片升级 MacBook Pro、重新设计的 iPad mini、针对校园用户的入门级 iPad、新款 Apple Watch 和入门款 AirPods。

消息称 SpaceX 探索太空广告业务,未来将用以太币等加密货币购买卫星广告牌

加拿大初创企业几何能源公司(GEC)证实,其正在 SpaceX 的帮助下,探索太空广告业务。GEC 首席执行官兼联合创始人塞缪尔·里德表示,该公司正在建造立方体卫星(CubeSat),该卫星的显示屏可以展示广告、标志等。GEC 计划使用 SpaceX 的猎鹰 9 号火箭将立方体卫星送入预定轨道,随后附着在立方体卫星侧面的自拍杆将拍摄显示屏。里德称人们将用以太坊等加密货币购买该广告牌。

苹果回应隐私争议:检测儿童不良图片不会留下后门,且只在美国提供

苹果公司计划推出的一项儿童安全功能旨在扩大对儿童的保护。但此举遭到包括 4000 多家组织以及安全与隐私专家、密码学家、研究人员、教授、法律专家和苹果消费者的公开反对。他们于周六签署公开信,谴责苹果有计划地破坏用户隐私和端到端加密机制。苹果公司在周五辩护称,不相信这款在用户设备上查找儿童性虐待图片的工具会留下削弱隐私的后门。

苹果重申,公司不会扫描机主的整个照片库来寻找虐童图片,而是使用密码学把图片与美国国家失踪与受虐儿童中心提供的已知数据库进行比对。

苹果称,这套系统已经开发多年,并不是为了政府监控公民而设计。而且,这套系统只在美国提供,只有用户打开 iCloud 照片后才能启用。

拆解发现华为 P50 Pro 采用三层 CPU:或因买不到麒麟 9000 专用内存

通常手机 SoC 与 RAM 运行内存芯片采取堆叠封装,也就是两层。维修机构发现,P50 Pro 使用的 RAM 芯片比华为 Mate 40 Pro 小一圈。华为采取的方式是在麒麟 9000 和 RAM 直接加了一转接层,从而变更为骁龙 888 专用内存的脚位。

维修机构猜测,可能是因为华为买不到麒麟 9000 的专用内存,所以采取了上述方式。

当然,这只是猜测。也有观点认为,也许是 P50 设计之初并未考虑骁龙方案,后期为了降低两套 SoC 方案的成本,索性借助转接层实现只需备货一种脚位的 RAM 芯片。

Facebook收购Giphy或涉嫌垄断,英国监管机构正在调查中

据财联社消息,当地时间周四,英国竞争与市场管理局表示,Facebook 收购 GIF 网站 Giphy 不利于社交网络平台之间的公平竞争,同时也让在线广告市场失去了一个参与者。CMA 警告称,如果 Facebook 收购 Giphy 引发的担忧最终得到证实,它可能要求 Facebook 出售 Giphy。

Giphy 是全球最大的 GIF 分享平台,曾为 Facebook 等多家社交平台提供服务。CMA 认为,Facebook 收购 Giphy 后,其竞争对手使用 Giphy 上的动图可能会受限,可能会导致竞争对手用户流入 Facebook旗 下社交平台。独立调查小组主席 Stuart McIntos 指出,Facebook 可能会要求竞争平台的用户交出更多数据才能使用这些动图。

对此,Facebook 发言人表示,该公司不同意 CMA 的初步调查结果,这次收购符合英国乃至全世界使用 Giphy 和 Facebook 服务的用户的最大利益,未来将继续与 CMA 合作,消除该交易妨碍公平竞争的误解。

Redis Labs 方面宣布,该公司现已正式更名为 Redis,从名称中删除了“Labs”一词。

官方表示,这一变化标志着公司和 Redis 开源项目的成熟;也反映了公司继续将 Redis 作为实时数据平台发展的使命。“更名反映了公司在引领开源 Redis 从流行的缓存系统演变为领先的实时数据平台方面所保持的核心作用”。公司更名不会影响开源项目 Redis 的 BSD 许可;同时,也不会影响去年引入的治理模型。

该公司已经为 Redis 开发了一套扩展的数据模型,包括,JSON、AI、图形和时间序列,并辅以强大的搜索和可编程引擎。此外,计划与 Redis 7.0 一起发布的 RedisRaft 还将使得 Redis 成为一个强一致性数据库。

英特尔的长期编译器专家 James Reinders 在一篇博客中透露,他们将在下一代英特尔 C/C++ 编译器中使用 LLVM 开源基础架构;并分享了一些相关信息。

“LLVM 有助于我们实现为英特尔架构提供最佳 C/C++ 编译器的目标。最新的英特尔 C/C++ 编译器使用 LLVM,可提供更快的编译时间、更好的优化、增强的标准支持,以及对 GPU 和 FPGA 卸载的支持......采用 LLVM 的好处很多,我将提供从经典编译器升级到基于 LLVM 的编译器的建议。我们致力于使其尽可能的无缝,同时为使用英特尔编译器的开发者带来众多好处。”

James 表示,他们在基于 LLVM 的编译器中专注于新功能和硬件支持。包括在其中添加了对 GPU 和 FPGA 的高度优化支持,同时继续致力于提供 CPU 优化。其基于 LLVM 的编译器将支持 SYCL、C++20、OpenMP 5.1 和 OpenMP GPU 目标设备支持。

“我们鼓励用户通过现在转向我们基于 LLVM 的 C/C++ 编译器来利用更快的构建时间、更高级别的优化和新功能。英特尔长期致力于 LLVM,以帮助持续创新以及我们对行业领先优化的不懈追求。”

并建议所有新项目都使用基于 LLVM 的英特尔 C/C++ 编译器,现有项目也应计划在今年迁移到新的编译器。James 称,在未来的某个时间点,经典 C/C++ 编译器将进入"Legacy Product Support"模式。这标志着对经典编译器基础的定期更新结束,它们将不再出现在 oneAPI 工具包中。

“全新的基于 LLVM 的 Intel C/C++ 已与经典版本达到同等水平,基于 LLVM 的 C/C++ 提供了我们拥有的最佳优化技术。我们建议所有用户现在就尝试新的 C/C++ 编译器,享受好处并提供反馈。”

此外,基于 LLVM 的英特尔 Fortran 编译器也正在进行中。目前,已有一个基于 LLVM 的 Fortran 编译器测试版提供了对 Fortran 的广泛支持,还有一些功能仍在开发中。

“假黄仁勋”刷屏之后,英伟达官方辟谣:只有14秒是虚拟的

GTC 2021 那场发布会,厨房中的老黄不是假的,发布的显卡也不是假的。

在近期召开的计算机图形顶级会议 ACM SIGGRAPH 2021 上,英伟达介绍了自研的 3D 仿真模拟和协作平台 Omniverse,并放出了「合成版老黄」的打造过程。

消息在极短的时间内引发了科技圈的轰动,很多人甚至在 Youtube、哔哩哔哩等平台反复回放今年 4 月 GTC 老黄的 Keynote 视频,企图分辨「真假黄仁勋」。

经国内媒体向英伟达中国团队确认,只有非常小的一部分视频是虚拟的。英伟达官方博客也进行了补充说明(之前版本未详细说明),并给出了数字替身出现的明确时间段:

也就是说,在 1 小时 48 分钟的 Keynote 视频中,只有其中 14 秒属于合成,放大了看,能明显看出来是虚拟黄仁勋、虚拟芯片。

而英伟达之所以在 SIGRAPH 上公布 GTC 2021 上的虚拟技术,是为了推广背后用到的 3D 仿真模拟和协作平台 Omniverse。

「Jensen Huang 的 GTC Keynote 演讲视频里,应该只有一小段他的全身动画及转场效果是 CG + AI 制作,其余部分都是真实影像,」一位 CG 领域资深研究人员在看过视频后表示:「技术本身是有新意的,可以参见 NVIDIA 今年 CVPR 的一篇论文,但其他方面就见仁见智了。」

Mozilla 近日透露,他们将在 Firefox 浏览器上引入一个新功能,这个功能将阻止用户在混合内容环境中下载不安全的文件。

混合内容指的是一个同时使用了安全连接和不安全连接的网站。想象一下以下情况:用户访问了一个使用 HTTPS 的安全网站,通过点击链接开始下载所需的内容。但链接指向的资源本身并没有采用 HTTPS,而是使用了不安全的 HTTP。这就是 Mozilla 所指的混合内容的一个示例。

通过不安全连接下载的文件很有可能会在传输过程中被网络中的中间人所篡改。按照计划,Firefox 浏览器最快将于 2021 年 9 月 7 日发布的 Firefox 92 中引入这个阻止源自 HTTPS 网站的不安全下载功能。

当用户遇到这种情况时,Firefox 不会自动下载文件,而是会在浏览器的下载面板上显示一个带有红色感叹号的警告,并指出文件因为潜在的安全风险而没有被下载。

点击下载面板中的箭头,可以查看到文件的其他信息和选项,用户可以基于提示和自己的判断继续下载该文件,或者是删除该文件。

之所以会阻止文件的下载只是因为不安全的连接,并非因为浏览器检测到该文件包含病毒或其他恶意内容。如果用户执意要下载的话,最好在运行前使用安全软件扫描一下文件以确保万无一失。

Mozilla 指出,根据统计目前大约 98.5% 的下载都使用了 HTTPS。换句话说,一旦这一变化在 Firefox 92 中正式推出,对大多数用户而言都不会造成影响。

Google 在 Chrome 86 中就引入了阻止不安全环境下的下载功能。如今大多数基于 Chromium 的浏览器都会阻止从 HTTP 来源的下载。在这种情况下,用户同样可以放弃或保留该下载,这与 Firefox 处理这些下载的方式类似。

随着 Firefox 补齐这个功能,目前市面上的主流桌面端浏览器都已支持这项功能。

OpenAI 开启 Codex 测试,一个将自然语言翻译成代码的 AI 系统

人工智能创业公司 OpenAI 在得到了微软等公司的大力支持后,如今推出新产品的速度也越来越快。继去年的 GTP-3 之后,如今时隔一年 OpenAI 宣布将通过 API 向企业和开发者提供访问其 Codex 程序的机会。

OpenAI Codex 是一个利用人工智能系统将自然语言翻译成代码的程序。

Codex 能够理解十几种编程语言,开发者可以用自然语言下达命令,Codex 会解析命令并执行它们。通过 OpenAI Codex 有可能为现有的应用程序建立一个自然语言界面。

Codex 还为今年 6 月 GitHub 推出的 Copilot 提供了技术支持,Copilot 为微软 Visual Studio 等开发环境中的整行代码提供建议。Codex 在数十亿行公共代码上进行了训练,并与一组广泛的框架和语言适配,能够适应开发人员编写的代码,以配合他们的编码风格。

根据 OpenAI 提供的信息,通过 API 提供的 Codex 模型在 Python 编程中具有最强的能力,但也 "精通" JavaScript、Go、Perl、PHP、Ruby、Swift、TypeScript 和 Shell 等编程语言。它在执行编程任务时能够考虑到上下文信息,包括转译、解释代码和重构代码。

虽然能力很强,但 OpenAI 在一篇论文同样也表示了 Codex 在现阶段仍然有很大的局限性,包括偏见和样本的低效率。OpenAI 的研究人员发现,该模型提出了语法错误或未定义的代码,调用了未定义或超出代码库范围的变量和属性。

更令人担忧的是,Codex 有时会建议一些表面上看起来正确但实际上并没有执行预期任务的解决方案。例如,当被要求创建加密密钥时,Codex 在部分情况下选择了明显不安全的配置参数,并推荐了有问题的软件包作为依赖。

也许是为了规避可能产生的风险,OpenAI 在文件中表示,像 Codex 这样的模型所引发的风险可以通过 "仔细" 的文档和用户界面设计、代码审查和内容控制来减轻。OpenAI 还将 "采取多管齐下的方法" 来减少滥用 Codex 的风险,包括限制请求的频率以防止自动化工具恶意使用。

  • 新的支持语言克罗地亚语
  • 语言西班牙文达到 100%
  • 修复了不能作为可配置工具栏一部分的操作黑名单

elementary OS 6 “Odin” 现已发布。官方表示,这是该平台迄今为止最大的更新。该版本专注于:

  • 使用户能够控制并表达自己

elementary OS 6.0 的主要更新内容包括有:提供了一种新的 dark style、使用 Flatpak 围绕应用程序沙盒进行了改进、整个桌面支持多点触控、改进了通知体验,支持通过 LVFS/Fwupd 进行固件更新、重写的电子邮件客户端、改进的网络摄像头应用,以及增强的安装程序等。

公告指出,elementary OS 6 利用尖端的沙盒技术在技术层面实施隐私和安全保护。在 OS 6 中,所有 AppCenter 应用程序现在都以 Flatpaks 格式打包和分发;这是一种现代容器格式,使应用程序和你的敏感数据彼此隔离。几个默认的 elementary OS 应用程序现在也作为 Flatpaks 分发。此外,elementary OS 6 利用门户让你能够控制应用程序之间的交互方式以及你的数据。应用程序必须以明确定义的方式明确请求权限。

另一方面,随着 elementary OS 6 对 Flatpaks 的全面支持,AppCenter 也进行了相应的更新。elementary OS 方面表示,虽然其一直在审查、批准和策划第三方 AppCenter 应用程序,但它们现在也作为沙盒Flatpaks分发,以获得更强大的隐私和安全。

当一个应用程序从列表中安装时,AppCenter 现在会在完成后显示一个应用内通知,因此它的打开速度更快。开发团队改进了应用标题的设计,包括按钮的对比。AppCenter 的通知现在提供了更丰富的 context,并为已安装的应用程序和更新改进了语言和 contextual badges。

JetBrains Academy 推出了新的免费的 Kotlin Basics 课程,其中包含了所有的 Kotlin 基础知识。JetBrains 方面表示,新课程“免费提供,允许学生在学习 Kotlin 的同时使用所有强大的平台功能”。如果学员每周学习 5 个小时,大约需要 10 周左右才能完成该课程。

在本课程中,学生将熟悉语言语法、概念和库,以及面向对象编程的基础知识。还将熟悉 Java 和 Kotlin 库等有用的工具,并学习如何使用文件系统。且在课程期间,学生可以提出问题,与社区互动并与他人一起学习。还可以查看其他学习者发布的解决方案,向他们学习;或通过发布自己的解决方案来帮助他人。

根据介绍,Kotlin Basics 展示了 10 个不同成熟度的真实项目、120 多个教育主题、以及 300 多个编码挑战。它还允许你在学习 Kotlin 的同时使用 JetBrains Academy 平台的所有功能。例如,一个按复杂程度来进行阶段划分的个性化学习计划,以帮助你循序渐进的扩展 Kotlin 知识。

与 JetBrains IDE 集成可让你在学习编程时获得使用专业开发工具的经验。

知识图一目了然地显示了所有主题的联系。它可以帮助你找到你的知识空白,并向你呈现你可以学习的主题来填补这些空白。

JetBrains Academy 的项目负责人 Nikolay Vyahhi 称,“起步总是一个挑战,但正确的学习方法可以抹平困难。我们 JetBrains Academy 相信基于项目的学习,在学习编程理论的同时,通过构建工作应用,一步步将知识应用于实践。我们很高兴将这种方法引入我们新的 Kotlin Basics 课程,帮助更多的学生以最有效和最舒适的方式获得新技能。”

Google 今天发布了 Android 12 的第四个 Beta 版本,并进入了发布的最后阶段。这也意味着 Android 12 的 API 和所有面向应用的行为都已最终确定。对于应用程序来说,现在的重点是兼容性和质量,以便在今年晚些时候随着 Android 12 的正式发布一同推出。

Android 12 Beta 4 已经达到了平台稳定性,这是一个里程碑,意味着在 Android 12 中所有面向应用的行为都已最终确定。这不仅包括官方 SDK 和 NDK API,还包括最终面向应用的系统行为和可能影响应用的非 SDK 接口的限制。从 Beta 4 开始,开发者可以发布应用的兼容性更新,因为平台不会再发生改变。

Google 要求所有的应用程序和游戏开发者从现在开始进行最后的兼容性测试,并准备在 Android 12 最终发布前尽快发布应用的兼容性更新。

对于 Android 12 来说,应用兼容性意味着你的应用能在新版本的平台上如期运行。你可以检查你的应用程序的兼容性,只需在设备或模拟器上安装你的应用程序的生产版本并进行测试。

因为每一个 Android 版本,Google 都会对平台进行整体的改变,以改善隐私和安全以及操作系统的整体用户体验。这些都会影响你的应用程序,所以开发者应该检查一下行为变化,并针对它们进行测试,然后向用户发布一个兼容的更新。

以下是一些需要测试的变化(适用于你的应用程序的 targetSdkV 版本为 31 或更高的情况)。

  • 前台服务启动限制 —— 应用程序不能再从后台启动前台服务。对于高优先级的后台任务,请使用 WorkManager 中的加速作业来代替;
  • 近似位置 —— 当应用程序请求精确位置的许可时,用户现在可以选择授予精确或近似位置;
  • 新的精确警报权限 —— 想要使用精确警报的应用程序必须申请一个新的权限 SCHEDULE\_EXACT\_ALARM;
  • 更安全的组件导出 —— 你的应用必须为任何使用意图过滤器的应用组件明确指定 android:exported 属性;
  • 自定义通知 —— 系统将标准通知模板应用于完全自定义的通知,并为应用名称、应用图标和展开/折叠数据提供支持;
  • 通知 trampoline 限制 —— 通知不能再使用 “trampoline” 启动您的应用程序 —— 一个启动目标活动的中间广播接收器或服务;

在未来几周,还有一个 Beta 版将作为候选发布版,供开发者进行最后测试。

Firefox 91 正式发布,更新内容如下:

  • 在 Total Cookie Protection 的基础上,我们增加了一个更全面的清除 Cookie 的逻辑,可以防止隐藏的数据泄露,让用户更容易了解哪些网站在存储本地信息;
  • Firefox 现在支持使用 Windows 单点登录功能来登录微软、工作和学校账户;
  • 打印时的简化页面功能回来了!打印时,在 "更多设置" \> "格式" 下选择 "简化" 选项,以获得一个简洁的页面;
  • HTTPS-First Policy:Firefox 隐私浏览窗口现在尝试使用更加安全的方式连接所有网站,只有在网站不支持的情况下才会退回到不安全的连接;
  • 在 macOS 上勾选 "增加对比度"时,Firefox 现在自动启用高对比度模式;
  • 用户交互的响应时间提高了 10-20%;
  • 增加了一个新的本地化语言:苏格兰语;

今天检查 svn 仓库,发现又有同学没按规定提交包含汉字的代码。我们规律,所有源文件中包含的汉字必须使用 UTF-8 编码方式,而不能使用 GBK 。

总这么人工检查也不是个事。所以我想写一个 svn 的钩子,在提交前检查。在仓库的 hooks/pre-commit.teml 加一行检查脚本应该就可以了。

我想用正则表达式匹配一下,可是想了想又觉得 UTF-8 和 GBK 的编码集有点交集,不太好做。btw, google 了一下,的确有人写过。

继续 google ,找到一篇跟我需求有点类似的文章。看了正文,觉得不太靠谱,然后继续看回复,觉得这方法可行。

然后定睛一看,原来文章是孟岩写的,回复是我自己三年多前回复在他的 blog 上的。 -_-

打算还是自己写个小程序做检查,不用现成工具了。

如果想区分一个完整的字符串是 GBK 还是 UTF8 其实蛮简单的。 虽然做不到 100% 有效,但也比上面的方法强许多。

gbk 的第一字节是高位为 1 的,第 2 字节可能高位为 0 。这种情况一定是 gbk ,因为 UTF8 对 >127 的编码一定每个字节高位为 1 。

另外,对于中文,UTF8 一定编码成 3 字节。(似乎亚洲文字都是,UTF8 中双字节好象只用于西方字符集)

连续汉字数量不是 3 的倍数的 gb2312 编码的汉字字符串一定不会被误认为 UTF8 。用了一些gbk 扩展字,或是插入了一些 ascii 符号的字符串也几乎不会被认为是 UTF8 。

一般说来,只要汉字稍微多几个,gbk 串被误认为 UTF8 的可能性极其低。(只需要默认不使用 UTF8 中双字节表示的字符)可能性低,这里还有另外一个原因。UTF8 中汉字编码的第一个字节是 1110**** ,这处于汉字的 gb2312 中二级汉字(不常用汉字,区码从 开始)的编码空间。一般是一些生僻字才会碰上。

本篇是应《程序员》杂志约稿所写。原本要求是写篇谈 C 语言的短文。4000 字之内 。我刚列了个提纲就去了 三千多字。 -_-

现放在这里,接受大家的批评指正。勿转载。


C 语言,从 1970 年代设计并实现之初,它就注定了带有强烈工程师文化的语言,而缺乏一些学术气息。它的许多细节设计,都带有强烈的实用化痕迹。C 语言因 UNIX 操作系统而生,是 UNIX 系统的母语。这导致在这个广泛应用的操作系统上开发,必须通过 C 语言的形式和系统进行交互。这不仅影响了 UNIX 一个平台上的软件,既而也影响了后来世界上最大的桌面系统 Windows ,以及越来越多的嵌入式平台。

由于大部分应用软件最终都需要和操作系统打交道,所以用来开发应用软件的语言,绝大部分也需要利用 C 语言完成和操作系统的通讯。这个世界上绝大部分流行的编程语言,都选择了用 C 语言来实现其编译器或解释器,以及基础部分的运行时库。无论 C 语言设计本身有何种缺憾,在今天,它已无可取代。

到了今天,大部分程序员不再需要逐个时间周期的去抠程序的性能。不需要刻意追求速度最快,最节省系统资源的软件。不需要写那些和系统内核紧密联系的程序。但 C 语言在此之外,依然有其重要的应用领域。我们可以把它作为对最终机器模型的高层次的统一抽象工具,而不必考虑机器环境的差异。经过 30 多年的发展,证明了 C 语言的确是对经典机器模型的最佳表述。仅仅通过增加了一个非常薄的胶合层就得到了一个清晰简洁的设计。正是这一点,使得 C 语言在计算机硬件高速发展的几十年中,一直生机勃勃。

我们在讨论 C 语言时,其实不仅仅涉及了 C 语言本身那用三十几个保留字构成的精简的控制结构和简约的语言特征。还包括了一套对 # 号打头的预处理部分(尤其是基于文本替换的宏处理),以及某些惯用的源代码组织方式(例如:所有的接口定义被定义在后缀为 h 的文件中,并通过预处理方式替换进源代码),和基本的程序库。

这几部分语言核心之外的部分相对独立。以至于使用 C 语言开发并不一定使用标准化的那些东西。C 语言对运行时环境的依赖是非常小的。

而编译预处理器又使得语言富有弹性,甚至可以写出违背 C 语言哲学的代码。著名的 IOCCC 大赛展示了许多常人无法理解的 C 代码。但实际上,C 语言主张代码清晰,表里如一。开发者和维护者都能很容易的预测每一行代码背后的行为。避免存在一些阴暗的角落藏着一些罕见的用法导致程序运行时出现诡异的行为。C 语言在发展过程中一直坚持着最小意外原则。而这一点,正是 C 语言的一个著名发展分支 C++

C 语言并不是绝对意义上最快的语言。但是它的效率非常好,在切合大部分机器模型并给出统一抽象的基础上,几乎没有其它语言做的更好了。这也是 C 语言哲学的一部分:在统一硬件抽象模型的基础上,尽可能的利用所在硬件环境的一切资源。有时候,C 语言程序员会走向某种极端。追求语言细节的优化,觉得某种代码的组织方式会比另一种方式更高效。但几乎总是错的。优化取决于对具体硬件的理解,以及对编译器如何翻译这些代码的了解。但这正是设计 C 语言想避免的东西。我们不必去争论在语句级上每行代码精确开销的优劣。

同时,C 语言的另一设计哲学就是让每行 C 代码尽量准确的对应相当数量的目标机器码。这使得程序员可以更为容易的理解程序的运行过程。让程序员脑海里可以实时地做一个源代码到最终控制流程的映射。基于这个思想,C 语言一直没有增加对结构进行运算的操作符(而 C++ 中把类或结构模拟成原生类型的做法相当普遍)。甚至于 inline 关键字也迟迟没有被标准化(inline 出现在 C99 标准中,而这个最新的 C 语言标准并没有被广泛接受),正是因为它某种程度破坏了这一点。

C 语言在坚持以上几点理念时,并非突出某个方面(比如追求性能),而是同时兼顾的。

C 语言并不是这个世界上唯一的编程语言,可惜的是,不是所有程序员都认识到了这点。对于把 C 语言作为自己唯一开发语言的程序员来说,很有必要开拓自己的眼界,这样反过来才能更为清晰的理解 C 语言的内在精神。并不是说,某某语言本身是用 C 语言来实现,那么 C 语言就可以以同样的方式,解决那种语言解决的问题(甚至更为高效)。一些 C 语言中的概念,到了另一种语言中,很可能用完全不同的方式展现出来。正如自然语言会影响人的思维方式一样,编程语言一样会影响人对某种算法的编码形式。在 C 里,我们总以为某些写法是自然而然的,但换了种语言却很可能并不尽然。

无论如何 C 语言的语法和设计影响了许多其它语言。最为彻底的是 C++ 。以及大多数程序员都能叫的出名字的一些流行语言:Java , PHP ,Javascript,Perl ,C#,D,Objective-C 等等。 这些给人造成一种错觉,新的语言取代了旧的,对老的语言做了改良和完善。最广泛传播的观点是,C++ 是 C 的一个超集,它能做所有 C 能做的所有事情,且能做的更好。持有这种观点的 C++ 程序员们甚至向把已有的各种 C 代码用 C++ 重新实现。但实际上,C 和 C++ 更应该被看成是相互平等的存在。C++ 更像是一种借用了几乎全部 C 语法(但还是有细微差异)的全新语言。它们在很多方面都有设计理念上的差异。C++ 企图完全兼容 C 的语法却不想完全继承 C 语言的理念,这使它背负了巨大的包袱。而 C 的另一个继任者:Objective-C ,抛弃了一些东西,则显得清爽一些。

回顾 C++ 出现的时代背景在于把面向对象当成解决复杂问题的“银弹”的年代。这使得 C++ 在发明之初,迅速的占领了大量原本是 C 语言的市场,甚至被看成是 C 语言的替代品。但 C++ 的拥趸们并没有等到这一天。历史证明,面向对象也不是“银弹”、最近十年,C++ 的粉丝们从 C++ 语言的犄角旮旯里挖掘出来的各种武器,让 C++ 语言变成了包含多种编程范式的巨无霸。却并没有让解决问题变得更容易。这并不完全是语言的问题,可能有很大程度上是面向对象等开发方法本身的问题。这也证明了 C 语言保持自身的简洁正是其生机昂然的源泉。

和浩如烟海的 C++ 书籍相比较。如果你已经是程序员,但还不了解 C 语言的话。学习 C 语言,只需要读一本书,而这本书没有第二选择,就是经典的《The C Programming Language》(K&R)。薄薄的一本就讲透了语言的方方面面。可惜的是,C 语言过于注重对机器模型的抽象,并不适合用来程序员入门。尤其是在国内的教材市场,充斥着大量糟糕的 C 语言教材。在这些拙劣的教材中,甚至把开发工具(比如特定的 C 语言开发集成环境)和特定的硬件环境(甚至是过时的 8086 内存模型)与语言教学混为一谈。

对于 C 语言不是母语的程序员来说,有充分的理由去学习一下 C 语言。那是低投入,高产出的。它会使你学会在硬件层次上思考问题(这或许对你是一个新的思维角度)。而且 C 语言已经非常稳定,不会再有(它本身也不希望有)大的变化,不用担心学到的知识会过时。C 语言在 1990 年制订出一个现在通行的标准( C90 )以来,在 C 的主流开发社区中几乎没有变过了。虽然,从 1999 年开始,C 语言委员会几经修订 C 语言的新标准( C99 ),但似乎并不被广泛接受。虽然有很大程度上,这是源于世界上最大的 C/C++ 商业编译器提供商微软对其不感兴趣。可在开源界,即使有 GNU C 对 C 语言新标准的不断推动,那些实际用 C 语言做开发的大佬们还是纷纷表示,新的标准还不是很成熟。新的特性也不是特别有必要。

笔者用 C99 开发有一些年头,但也只使用了其中一个子集,不太敢在正式项目中完全推广。至于 C 语言近年来的发展,我个人比较欣赏苹果公司对 C 语言添加的 blocks 扩展以用来实现 closure 。但并不看好这些新特性会迅速融入 C 语言社区。

C 语言从语言角度上讲,最大缺陷在于要求程序员自己去做内存管理。用 C 语言去处理复杂的数据结构,程序员大部分的时间都花在了这上面,并且滋生了无数 bug 。调试 C 程序变成了一项独立于编写 C 程序的技能。防止缓冲区溢出、防止数据读写越界、正确的动态回收内存、避免悬空指针,这些在大部分语言看起来不可思议的关注点,在 C 语言程序员眼里变得稀松平常。甚至是衡量 C 程序员技能经验水平的重要标志。可要知道,这些和具体问题的解决过程无关。

也有人试图在 C 语言层面解决这个问题,例如以库形式提供垃圾回收的机制(笔者也曾做过类似尝试)。但 C 语言本身的设计使它无法成为一个完美的解决方案。同样的问题也存在于 C++ 。现在看来,不对语言做大的改造,很难回避。可改造本身又违背了 C 语言一贯的哲学。C 语言的发明人之一的 Ken Thompson 近年来参与了新的 Go 语言的设计和实现,可以看成从另一角度对新的程序开发语言的尝试,可那已经不是 C 。

这个问题在一定程度上也促使了 java 的诞生。Java 采用了虚拟机和字节码的方式改造了底层的机器模型。并在底层模型的基础上加入了垃圾回收机制。并在语言层面取消了指针。在 C 语言的原生地,也有更多的动态(脚本)语言出现。先是有 awk 这样的简易语言,后有 perl ,再是 python 等的流行。在 Unix 风格下,程序员倾向于为特定领域设计特定的语言。C 和 Unix 的设计哲学是一体的。它们都鼓励清晰的模块化设计。让模块之间独立,再用薄的胶合层联系起来。脚本语言在现代类 Unix 系统上大量出现,并充当这种粘合工作就是一种发展必然。而原本的充当粘合部分的脚本语言,也逐步发展起来,远远超出脚本的用途范畴。做为程序员,尤其是 C 程序员,必须对它们有所了解并掌握其中的一些,才能适应现代的挑战。

我们不应该指望一门语言解决所有的问题。可至于 C 语言本身,它将在很长的一段时间,带着它的优雅和缺陷,继续扮演它在计算机世界中重要的角色。


ps. 命题作文真难写啊。

这段时间的工作是把上次提到的 系统实现了。而写这篇 Blog 的促因是 twitter 上有同学想让我谈谈对 的看法。哦,看似既然是语言之争。C 好,还是 C++ 好。但这次他平和了许多。Linus 惯有风格依旧,但少了些须 中的刻薄。

我想说, 哪一点都不可忽略。Linus 这次强调的大约是第三点,也是 C++ 程序员们不屑一顾的一点。可对于多人协作构建的项目,这一点实在是太重要了。这并不是人人都聪明就能回避的问题。如果程序员们都足够睿智,反而更能意识到沟通之成本。其实即使是你一个人在做整个项目,从前的你和现在的你以及将来的你,同样有沟通(记忆)的成本。人不可能两次踏进同一条河流。

我的观点在于,如非必要,勿增概念。这是我这次翻新资源管理系统的初衷。在项目组内,这种大的修改是反对多过赞同的。我尚无能力像说服自己那样说服每个人。虽然我及其主张项目演化中的民主,但这一次稍显独断,实在是不得已。因为我觉得这是个不明显的重大缺陷。虽然老的设计它精巧,且可以很好的工作,但它不适合长期保留。

理想的大项目,应该是每个人专心做自己的一块东西,它涉及的外部部分用极少的文档或易于表达的概念定义清楚:无论是程序接口、对资源的占用、适用的范围等等。尤其是弱化 framework 这种联系方方面面的巨无霸。

说回这次实现的 VFS 模块。实现还是比较简单的。但是设计很困难。难点在于,虽无可避免的有一些 framework 的倾向(比原来的系统要弱化许多),但怎样让后面具体的文件系统跟这个小型 framework 交流最少。架子主要解决的是内存资源管理问题和用 cache 提升索引性能的问题。

我定义了两个内部数据结构,借用 linux 的 vfs 中的概念表达。一种叫 dentry 描述目录项,一种叫 inode 描述文件项。但没有暴露这两个数据结构的内部布局。用户扩充的时候,需要给出各一个额外的数据指针来扩展自己的结构。。实际应用的时候,没有定法。

所有函数都应该是可重入的,但暂不要求线程安全。以此实现文件系统的嵌套 mount 。起初,我认为实现一个 zipfs 会很容易。可以 mount 上通用的 zip 文件使用。实际实现时,发现无论是通用的 还是另一个使用执照稍微麻烦一点的 ,都不提供高效的 seek 接口。我花了一整个晚上研究 zip 的文件格式和解码算法。发现对于这种流式数据压缩,很难做到特别高效的 seek 算法。

利用 zlib 的底层 api ,我想了个办法来提高 zlib 中带的 minizip 库的 seek 效率(目前必须通过假读来实现),不过也不可能达到 O ( LogN ) 的水平。所以我放弃了这个打算。没有高效的 seek 接口,坏处在于嵌套的 mount zip 文件中被打包的 zip 文件性能会很差。当然,可以选择在 zip 被打包进 zip 时不压缩,这样稍微改造一下 minizip ,就能把 seek 提高到 O(1) 的水平。我权衡了一下,还是把此类需求留到以后自己设计一个新的包格式为佳。(采用分块压缩即可)

最终我实现了一个基本的 rootfs ,一个 memfs 用于在内存中创建文件和目录(主要用于创建出最初的 mount 点),一个 nativefs 用于把本地文件系统上的目录树以只读方式映射给引擎,一个 zipfs 实现开发期基本的打包方案。未来可能会增加一个可写的文件系统,用于保存一些本地设置。一个自定义的包方案。

同时在同事的协助上,把 engine 中老的资源操作的接口迁移到新系统上。

btw, 我还考察了。不过由于繁杂的执照问题,暂时没有采纳。如果人力充足的话,日后倒可以找人来加上。

今天晚上继续读 《Masterminds of Programming》,忍不住又翻译了半章关于 Forth 之父的访谈。我以前读过几篇更早时期关于他的访谈,部分了解他的观点。小时候还特别迷 Forth 。这位神叨叨的老头很有意思。

没看过原来的译本,只是自己按自己的理解翻了第 4 章 Forth 的前一半。我也算对 Forth 很有爱的人吧,也还了解 Forth 里诸如 ITC (Indirected-threaded code) 这种术语到底指的什么,不过还是觉得翻译有点吃力。

对 Forth 同样有爱的同学们姑且看之吧。


Forth 语言及语言的设计

Chunk: Forth 是一门计算机编程语言,其语法规模简到极致。把参数栈专门独立出来是语言的一大特色,这使得子程序调用非常高效。因此,语言采用后缀表达式(先列参数,再跟操作符),并鼓励把程序高度细分成众多短小的子程序的风格。这些子程序共享栈上参数。

我曾经读过关于 Forth 这个名字的介绍,据说是象征着第四代软件开发。你可以给我们多介绍一些吗?

Chunk: Forth 源于 "Fourth (第四)" 这个词。暗指 “第四代计算机语言”。据我回忆,我跳过了一代。FORTRAN/COBOL 是第一代;Algol/Lisp 是第二代。这些语言都强调语法。语法越详尽,越能检查出错误。但大部分错误是语法错误。我决定将语法元素减到最小,而强调其语义。被加载的 Forth 词就真正的表达其含义。

你把 Forth 当成一个语言工具集。我这样理解这个视角:给出相对其它语言更简单的语法,提供比其它语言所用的更短的词构成一个词汇表的能力。我还漏掉了什么吗?

Chunk Moore: 就是这样。其中关键一点是,我们尽其可能的分解。一段 Forth 程序有大量的小词构成。相对来看,同样的 C 程序用到的词则少的多,而且每个词都要大的多。

我所谓短小的词,是指可以用一行源代码定义出来的词。你可以用刚定义出来的词来定义下一个新词,如此层叠,最终得到数千个词的定义,最终形成了语言。这里面临的挑战是:1) 判断哪些词是有用的。2) 记住所有定义出来的词。我现在正在编写的应用程序有上千个词。所以我写了个工具来搜索这些词,不过你必须先记得有这个词,大概怎么拼才找得出来。

好了,现在有了种完全不同的编程风格,程序员需要一点时间才能适应。我已经看过太多 Forth 程序就像是从 C 程序直译过来的。这样做可不对。正确的做法是从零开始。关于这个工具集还有一个有趣的地方,只要你好好做,定义出来的新词和内核中预定义的词并无不同。它们一样高效,对外的意义也相同。定义你自己词是没有额外开销的。

外部结构看起来是由许多小词组成,这一点是源于 Forth 的实现吗?

Chunk: 这是我们非常高效的子程序调用序列的结果。这里面没有参数传递,因为语言是基于栈的。所有的一切仅仅只是调用和返回。而栈暴露在外。让机器识别的语言编译在一起,进入和退出字程序只需要逐字翻译成一条 call 指令和一条 return 指令。你总可以进一步的达到汇编语言层面,得到等价的指令。你可以定义一个词刚好执行一条真正的机器指令,而不是做一次子程序调用,这样,它可以匹敌任何其它语言的效率,甚至比一些语言更高效。

这样就没有 C 调用的消耗

Chunk: 没错。这赋予了程序员巨大的灵活性。如果你能聪明的分解问题,你不仅仅只是高效的完整任务,还使得完成的过程变得格外易读。

另一方面,如果你做的很糟糕,你最终的代码世界上只有你一个人能解读――连你那全知全晓的经理都看不懂你写的代码。你可以写出天书。所以,这是把双刃剑。你能做的完美无暇,也能弄得污七八糟。

你能说点什么(或展示点代码)让用别的编程语言的开发人员对 Forth 一见钟情吗?

Chunk: 让有经验的程序员对 Forth 感兴趣挺难的。这是因为他已经为他正在使用的语言/操作系统相关的工具学习做了大量的投资。为他的应用程序积累了许多。即使是告诉他们,Forth 会更小,更块,更简单,也及不上把所有东西都重写的代价。一个新手程序员,或是一个没这么干扰的需要写点代码的工程师可能更容易接受一点。嗯,或是有某个有经验的程序员要开个新项目,而这个项目有些新的约束条件,比如我现在在做的多核芯片上做开发。

你提到你见过的大量 Forth 程序看起来像 C 程序。那么你自己怎么设计一个更好的 Forth 程序的?

首先,你估计会有一些 I/O 信号要去产生,好吧,就来产生它们。这样你就写一些代码控制这些信号的产生过程。然后你一点点来,知道最终构建出高层的词。假定你把它起了个名字叫 go ,接下来你敲一下 go ,一切如期发生。

我不信任由顶向下的系统分析方法。他们判断问题是什么,然后再分解,分解出来却很难实现。

领域驱动设计建议以客户的词汇来描述商务逻辑。在创建词库和使用你问题域的术语之间有什么联系?

Chunk: 最好是程序员在开始写代码前了解那个领域。我会和客户沟通。我会倾听他用的词汇,并试着用这些词,这样他也能明白程序在做什么。Forth 因其采用后缀记号法使代码费用易读。

如果我写一个经济有关的应用程序,我可能会用一个叫作 "percent" 词。你可以在代码中写 "2.03 percent" 。这里 percent 的参数就是 2.03 ,一切都和看起来那样自然。

一个从穿孔卡片计算机时代开始的项目居然到了互联网时代的现代计算机上还这么有用。Forth 于 1968 年在 IBM 1130 上设计并运用。它到了 2007 年继续为并行处理所用,这真奇妙。

Chunk: 其间 Forth 也进化了。不过 Forth 可能是最为简洁的计算机语言。它没给程序员附加任何约束。他(她)可以瘦的层次化方式定义一些词精确切合问题的原貌。

你在设计程序时,是否把让程序读起来像英文一样做为一个目标?

Chunk: 在非常高的层次上看,是这样。但英文并非功能性描述的好语言。英语不是设计来干这个的,不过英语和 Forth 有个共同的特性,就是你能定义新词。

你用以前定义好的许多词通过解释的方式来定义新的词汇。对于自然语言来说,有可能不严谨。如果你查字典的话,会发现有些是循环定义,你查不到本质内容。

是不是把注意力转移到一堆词上面,比 C 里出现的那些各种括号来说,Forth 因此能写出更好味道的程序?

Chunk: 希望如此。这使得 Forth 程序员去关注事物的外貌,而不仅仅是其功能。如果你能组织起一系列的词,把它们有序的排列起来,会感觉很好。这正是为什么我开发了 colorForth 。我曾为 Forth 里老的语法烦恼。比如,按现在的方法,你要做注释,就必须用一左括号和一右括号来括起来表示。

我看着所有标点符号说,“好吧,可能还有更好的方式。” 这更好的方式最麻烦的一点是,源代码中每一个词都需要附加一个标签。如果我能忍受这点开销,所有的符号都另人舒服的消失了,取而代之的是,每个词都有了颜色。对我来说,这是个优雅的表达其功能的方式。

我遭到了色盲群体的抨击。他们对我试图把他们排出程序员行列的做法义愤填膺。不过某人最后想了个招,用字体的区分代替颜色的区分,这也是个不错的方法。

关键点在于,每个词有个四位的标签,这能区分出 16 种事物。编译器可以立刻感知它要做什么,而不用从上下文去推断。

第二代和第三代语言都皈依了极简主义,举例来说就是实现了 meta-circular bootstrapping (圆环自举,靠自身把自身运作起来)。Forth 是在对语言的概念定义以及硬件需求量方面的极简主义的最好的例子。这是当年的时代特征或是你做出的跨时代的创举吗?

Chunk: 非也。当时再三考虑的设计目标是尽可能的做一个最小的内核。只预定义最为必要的几个词,然后让程序员再去添加他觉得合适的。主要因素是可移植性。在那个时代,大打的小型计算机,接着又是一大坨微型计算机。而我必须把 Forth 弄到如此之多的机器上去。

我想干这点事能尽量轻松点。我要干的就是弄出一个百来个词的内核,以此能够组成一个,我叫作操作系统,但其实不完全是操作系统的东西,这个东西再给出几百个词为人所用。接下来你就能在这上面做开发了。

我来提供做前两个阶段的工作,让程序员去做第三个。我也经常做应用程序开发程序员。定义我知道的词总是很有必要。头一百个词可能用机器语言或汇编语言定义,至少是直接和特定平台打交道。第二和第三百个词可以是高层次的词,在较低层次最小化机器依赖性。接下来,应用程序就能最大限度的做到机器无关了,这样很容易把程序从一台机器移植到另一台上。

当初你能在第二阶段之上方便的移植吗?

Chunk: 绝对如此。比如我有个文本编辑器,用来编辑源代码的。它总是在各种机器上不需要修改任何地方都能用。

坊间流传着一个传说,每次你看到一台新机器,你就立刻动手把 Forth 移植到上面。是说的这个吗?

Chunk: 没错。实际上对于理解一台机器如何工作;了解那些可以用来更容易实现 Forth 标准包里词的诡异机器特性;这是条最简单的途径。

Chunk: 代码是一个很微妙的概念。每个 Forth 词在字典里有一个入口。对于基于直接线索的编码(direct-threaded code ,有译为直接串线编码),遇到引用每个词的位置直接指向要执行的代码。而基于间接线索的编码(indirect-threaded code 有译为间接串线编码) 则指明一个包含了代码所在地址的位置。这使得地址之外的信息能被访问到――比如,一个变量的值。

这可能是最为紧凑的词组织方法了。它可以等价于基于直接线索的编码和基于子程序的编码(subroutine-threaded code ,即类似 C 语言编译成的那种直接调用子程序方式)。当然这个概念和术语在 1970 年时还没有。但对我来说,这是实现各种各样词的最自然方式。

Forth 会如何影响计算机系统的未来走向吗?

Chunk: 这已经在发生了。我在微处理器优化方面干了 25 年,最新近的一个多核芯片的核心是 Forth 计算机。

Forth 提供了些啥?作为一个简单的语言,它使得这样一台简单的计算机:有 256 个字的本地内存;两个下压栈;32 条指令;异步操作;易于和相邻机器通讯;可以很小,且功耗极低。

Forth 鼓励被高度分解的程序。这非常适合在多核芯片上做并行处理。大量小程序鼓励你每一个都深思熟虑的设计。这样最终你可能只需要写 1% 的代码就够了。

只要我听到有人吹嘘代码达到了上百万行,我就知道他们肯定前面理解错问题了。当代没啥问题需要写几百万行代码。要么是程序员太粗心、要么项目经理太混蛋、要么就是为兼容一些不存在的需求。

使用 Forth 对需要小计算机编程是个巨牛叉的策略。别的语言都提供不了相当的模块化能力和扩展性。尤其计算机越来越小,它们之间必须做网络化协作(智能微尘?),这就是未来的环境。

这听起来像 Unix 的重要原则之一:以许多程序,每个只做一件事,相互作用。这依旧是当今最好的设计吗?在一台机器上跑多个程序会被通过网络运行的多个程序取代吗?

Chunk: 让代码跑在多个线程上的这个概念,被 Unix 和别的操作系统所实现。这是并行化处理的先驱。但这里有些重要的差别。

大的计算机承担得起多线程通常会要付出的一些代价。最终弄出个庞大的操作系统。对于并行化处理来说,永远都是计算机越多越好。

在资源一定的情况下,更多的计算机意味着更小的计算机。但很小的计算机是承担不起在大计算机上承担的代价的。

小的计算机的网络化会发生在芯片之上,通过 RF 连接的芯片之间。小的计算机内存也小。操作系统无处容身。计算机必须自治,自己要有能力保持通讯。因此通讯环节必须简单――没有那些煞费苦心的协议。软件也必须紧凑高效。最为理想的应用程序就是 Forth 了。

那些需要数百万行代码筑就的系统将会淡出历史舞台,是它们造就了巨大的中央计算机。分布式技术需要条不同的思路。

一门语言若是设计成支持繁杂的,拘泥于语法条条框框的代码,就会鼓励程序员写出巨大的程序。并以此自得,洋洋得意。没什么压力去寻找紧凑的方案。

以繁杂句法定义出来的语言生成的代码也可以很小,但通常做不到。以语法默示的实现流程导致了笨拙而不那么高效的目标代码。这对于小的计算机来说并不合适。一门良好设计的语言在源码和目标码之间存在一对一的联系。这向程序员昭显了源码如何生成为最终代码。注重性能、减少对文档的需求,使得程序员感到满足。

Forth 设计成对于源代码和二进制输入的目标代码皆很紧凑,这也是嵌入式开发中广泛使用的原因。不过在许多其它领域程序员总有别的理由去用别的语言。是不是说这些语言的设计的某些方面只是增加了原代码或是目标码的开支吗?

Chunk: Forth 的确很紧凑。一个因素在于他的语法量很小。

其它语言看起来是故意的增加一些语法,弄出点冗余,可以帮助语法检查以及错误检测。

Forth 没提供啥机会用来做错误检查,因为它没有冗余信息。这也使得源代码非常紧凑。

我感觉其它语言几乎所有的错误都出在语法上。设计者看起来为程序员犯下编译器就都能找出来的错误创造了条件。这没啥经济价值。这不是为写出正确的代码自找麻烦吗。

比如说类型检查吧。不同的数字类型之间的赋值错误会被侦测到。无意中带来的后果是程序员必须自己来转换类型,有时就是想回避类型检查而得到他们真正想干的事情。

语法导致的另一结果是它必须适应所有的应用程序的意图。这就会越来越复杂。Forth 是一个可扩展的语言。程序员可以创建一些别的语言只能通过编译器的改进才能获得同样性能的结构。而所有的能力不需要一开始就想好提供出来。

Forth 的特征之一是使用后缀操作符。这能简化编译器,从源代码到目标代码给出一对一的关系。程序员对他写的代码的充分理解能增加代码编译后的紧凑程度。

**许多最近的计算机语言(尤其是 Python 和 Ruby)都把可读性引为其关键好处。Forth 在这方面与之相较可以从中学到并保持些什么?Forth 能在可读性方面的定义方面传授给其它语言些什么东西?

Chunk: 计算机语言都宣称要可读。但他们并不可读。或许对懂这门语言的人来说是可读的。但初学者还是稀里糊涂的。

问题就在于晦涩、武断、隐秘的语法中。那些小括号啦,& 符啦,等等。你试着学习它们为啥出现在那里,最终推断,其实没什么好理由。但是你还是要按规则办事。

你无法说这门语言。你必须像 Victor Borgia 那样叽里呱啦的把符号都念出来。

Forth 通过最小化语法来减轻这个问题。它用的哪些个神秘符号,@ 和 ! 可以读成“取”和“存”。这些个使用符号是因为出现的太频繁了。

程序员被鼓励使用自然语言中的词汇。它们不通过标点的间隔组织在一起。若是你选好了词,你就能构造出有意义的句子。实际上有人用 Forth 来写诗。

另一个优势是后缀表示。一个像 “6 英寸”这样的短语能把操作符“英寸”作用于参数 6,这是非常自然的表达方法。非常的可读。

另一方面,程序员的任务就是开发吃一组词来描述问题。这个词典会变得非常大。读者需要了解整个词典使得程序可读。程序员就必须好好的定义词。

总而言之,用任何语言,这些都会影响程序的阅读。

你如何定义你的工作中如何定义成功?

Chunk: 一个优雅的解决方案。

人们并没有用 Forth 编程。Forth 就是一个程序。他们添加新的词创建一个词典来定义问题。当正确的词被定义出来,一切都浑然天成。接下来你就能你可以互动地解决所有相关问题的任何方面。

举个例子:我可以定义一些词来描述一个电路。我以后想把这个电路加到一块芯片里去,显示电路的布局,校验设置规则,模拟跑一下。用来干这些事情的词决定了应用程序的形态。如果精心选择这些词,提供一个紧凑而有效的工具集,然后我就搞定了。

你在哪学会写编译器的?在那个年代每个人都必须去写编译器吗?

Chunk: 哦,我在六十年代去了趟 Stanford ,那里有组研究生正在写一个 ALGOL 编译器――Burroughs 5500 用的版本。当时我想他们不过才三四个人,就三四个人坐在那写一个编译器,我那个内牛满面啊。

我想,“靠,他们要能做,我也能做。”然后我就干了。其实一点也不难。当年写编译器还是有点神秘西西的。

现在其实也还是神秘西西的

Chunk: 嗯,不过已经没那么神秘了。你看现在新语言一个个的冒出来,我不知道算解释型的还是编译型的,不管怎样,有黑客风范的人都想做一个。

操作系统是另一个稀奇古怪的东西。操作系统吓人的复杂,而且完全没用。它是 Bill Gates 成功向世界推销出去的一个光彩夺目的概念。这可能是世界上出现的最大的骗局。

操作系统对你来说做着绝对的无用功。其实你有这样一些东西就够了:一个叫作磁盘驱动程序的子程序,一个叫什么什么通讯支持的子程序,而在现代社会,操作系统啥也没做。实际上,Windows 花了大量时间在包装层上,或是诸如磁盘管理器这样不相干的东西上。你有了上 G 的磁盘,有了上 M 的内存。世界格局发生了变化,使得操作系统不那么有用了。

Chunk: 你对每个设备有一个子程序。那是一个库,而不是操作系统。你需要那个就装载哪个。

在工作间隙后,你怎么继续编程?

Chunk: 在被困扰的时候,我不会中断我的编码。我会充满热情的思考问题,做梦都会想着它们。我想这是一个 Forth 的特质:在小段时间(以天计)内全神贯注的解决一个问题。这帮助 Forth 应用程序自然的被分解为一个个子项目。几乎所有的 Forth 代码都简单易读。当我真的要做一些偏激的事情,我会做好注释。好的注释能帮我以后回到问题中,不过重新阅读和理解代码总还是有必要的。

你在设计或编程中犯过最大的错误是什么?你从中学到点什么?

Chunk: 20 多年前,我想为设计 VLSI 芯片开发个工具。我的新电脑上没有 Forth ,因此我就想用另种方案,写机器语言。不是汇编,就是用 16 进制码敲机器指令。

我像我写 Forth 程序那样编写代码,分层次的定义出需要简单的相互有关的词。最终搞定了。我用了这个东西 10 年。但是无法维护,也没有文档。最终,我用 Forth 重写了一遍,这个玩意变得更为小巧而且简单多了。

我的结论是,Forth 比机器语言更高效。一部分源于其交互性,另一部分是因为它的语法。Forth 的一个很漂亮的方面是,数字可以用计算它们的表达式文档化的表达。

书是好书,可惜翻译这本书需要对各种语言的深入研究,看起来译者有点力不从心。出版社打算重新做这本书。受编辑所托,我校对了其中第七章:有关 Lua 的一段。原文读下来拍案叫好。可惜译文许多地方看起来有些词不达意。许多在口语化交流中提到的术语被忽略了做了错误的翻译。有些部分应该是对 lua 理解不够而没能表达清楚。

仔细校对了两段后,我干脆放弃原译本,自己动手翻译了一份(保留了不到 1/4 原来的译文)。虽然个人能力有限,但也算是每句话自己都看明白了再译的。虽说有些地方没有直译,但也算没有夹带私货。

这里贴出一段,希望大家阅读愉快。


Lua 是一门非常之小,但五脏俱全的动态语言。它由 Roberto Ierusalimschy、Luiz Henrique de Figueiredo 和 Waldemar Celes在1993年创建。Lua 拥有一组精简的强大特性,以及容易使用的 C API ,这使得它易于嵌入与扩展来表达特定领域的概念。Lua在专有软件界声名显赫。例如,在诸多游戏中,比如

你是如何定义 Lua 的?

LHF:一种可嵌入,轻量,快速,功能强大的脚本语言。

Roberto:不幸的是,越来越多的人们使用“脚本语言”作为“动态语言”的代名词。现在,甚至是 Erlang 或者 Scheme 都被称为脚本语言。这非常糟糕,因为我们无法精确的描述一类特定的动态语言。在最初的含义解释中,Lua 是一种脚本语言,这种语言通常用来控制其它语言编写的其他组件。

人们在使用Lua设计软件时,应该注意些什么呢?

Luiz:我想应该是用 Lua 的方式来做事。不建议去模拟出所有你在其它语言中用到的东西。你应该真的去用这个语言提供的特性,我想对于使用任何一门语言都是这样的。就 Lua 来讲,语言的特性主要指用 table 表示所有的东西,用 metamethod 做出优雅的解决方案。还有 coroutine 。

Lua 的用户应该是哪些人呢?

Roberto :我认为大多数没有脚本功能的应用程序都能从 Lua 中受益。

Luiz:问题在于,大多数设计者很长时间都不会意识到有这种需求。当已经有了诸多用 C 或 C++ 编写的代码,为时已晚。应用程序设计者应该从一开始就考虑脚本。这会给它们带来更多的灵活性。而且这样做还可以更好的把握性能问题。因为这样做以后,会迫使他们去考虑程序中到底哪里是性能关键,而哪些地方无伤大雅。而这些性能不太重要之处,就交给脚本去处理,开发周期短,速度快。

从安全性的观点来看,Lua 能为程序员提供些什么呢?

Roberto:Lua 解释器的核心部分被构建为一个 “独立的应用程序(freestanding application)”。这个术语来自 ISO C,大意是说,这部分不使用任何跟外部环境有关的东西(不依赖 stdio、malloc 等)。所有那些功能都由扩展库来提供。使用这种体系结构,很容易让程序限制对外部资源的访问。具体来说,我们可以在 Lua 自身的内部创建出一个沙盒,把如何我们认为危险的操作从沙盒的外部环境中剔除。(比如打开文件等)

Luiz:Lua 还提供了用户自定义的调试钩子,用它可以监视 Lua 程序的执行。这样,在 lua 中运行时间过长或是使用了过多内存的时候,我们可以从外部中断它的执行。

Lua 有什么局限性?

Roberto:我认为 Lua 的主要局限是所有动态语言共有的。首先,即使是利用最先进的 JIT 技术(Lua 的 JIT 是所有动态语言 JIT 中最好的之一)也达不到优秀静态语言的性能。其次,一些复杂的程序从静态分析中受益匪浅(主要是静态类型)。

是什么促使你决定使用垃圾收集器?

Roberto:Lua 从第一天开始,就一直使用垃圾收集器。我想说,对于一种解释型语言来讲,垃圾收集器可以比引用计数更加紧凑和健壮,更不用说它没有把垃圾丢得到处都是。考虑到解释型语言通常已经有自描述数据(通过给值加上标签之类的东西),一个简单的标记清除(mark-and-sweep)收集器实现起来极其简单,而且几乎对解释器其余的部分不会产生什么影响。

对于无类型语言(untyped language),引用计数会很重量。没有静态类型,每次赋值都可能会改变计数,对变量的新值和旧值都需要进行动态检查。后来尝试过在 Lua 中引入引用计数,并没有提高性能。

你对 Lua 处理数字的方式满意吗?

Roberto:从我的经验来看,计算机中的数字老是会给我们带来一些意外(因为它们也来至于计算机之外!)。至于说 Lua 使用 double 作为唯一的数字类型,我认为这是一种合理的折衷方案。我们已经考虑了很多其他可选方案,不过对于 Lua 来说,这些方案要么太慢,要么太复杂,要么太耗内存。对于嵌入式系统,甚至使用 double 也不是一种合理的选择,因此,我们可以使用一个备选的数值类型,比如说 long ,来编译解释器。

你为什么选择 table 作为 Lua 中的统一数据结构?

Roberto:从我的角度,灵感来自于VDM(一个主要用于软件规范的形式化方法),当我们开始创建 Lua 时,有一些东西吸引了我的兴趣。VDM 提供三种数据聚合的方式:set、sequence 和 map。不过,set 和 sequence 都很容易用 map 来表达,因此我有了用 map 作为统一结构的想法。Luiz 也有他自己的原因。

Luiz:没错,我非常喜欢 AWK ,特别是它的联合数组。

程序员可以从 Lua 中的 first-class 函数中获得怎样的价值?

Roberto:50多年来,虽然名称各异:从子程序到方法,“函数” 已经成为编程语言的主要部分,因此,对函数的良好支持为所有语言必备。Lua 支持程序员使用函数式编程领域中的一些功能强大的技术,比如,把数据表示成函数。例如,一种形状可能用函数来表示,给定 x 和 y ,可以判断这个点是否在这个形状内。这种表示方式可以用于一些操作,比如联合和交集等。

Roberto:闭包自始至终我们都想在 Lua 中实现:它简单、灵活、功能强大。从第一版开始,Lua 就把函数做为一等值 ( first-class value ) 对待。这被证明非常有用,即使是对于没有函数式编程的“常规的”程序员来说也是一样。而不支持闭包的函数,其实用价值就会大打折扣。顺便说一句,闭包这个术语来源于一种实现技术,而不是指它本身的特性。从特性描述上来说,闭包相当于“带词法作用域的一等函数”,当然用闭包这个术语更为简短。

你打算如何处理并发问题?

Roberto:我们不信任基于抢占式内存共享的多线程技术。在 HOPL 论文中,我们写道:“我们仍然认为,如果在连 a=a+1 都没有确定结果的语言中,无人可以写出正确的程序。” 我们可以通过去掉抢占式这一点,或是不共享内存,就可以回避这个问题。而 Lua ,提供用这两种方式解决问题的支持。

使用协程(coroutine),我们可以共享内存,但不是抢占式的。不过这个技术利用不到多核机器。但在这类机器上,使用多“进程”就能极大的发挥其性能。这个我提到的“进程”是指在 C 里的一个线程,这个线程维护自己独立的 Lua 状态机。这样,在 Lua 层面上,就没有内存共享使用。在《Lua 程序设计第二版》[Lua.org] 中,我给出了这种方式的一个原型。最近我们已经看到有些库支持了这种方式(比如 Lua Lanes 以及 luaproc)。

你没有支持并发,但你为多任务实现了一个有趣的解决方案:非对称式协程。它们如何工作的?

Roberto:我有一些 Modula 2 语言的经验(我的妻子在她的硕士论文工作中为 M-code 编写了一个完整的解释器),使用协程作为协作式并发以及别的控制结构的基础设置是我一直偏爱的方法。然而,Modula 2 中那种对称式协程,在 Lua 中行不通。

Luiz:在我们的 HOPL 论文中,对那些设计决策全部做了极为详细的解释说明。

Roberto:我们最终选择了非对称式模型。它的基本思想非常简单。通过显式调用 coroutine.create 函数来创建一个协程,把一个函数作为协程主体来执行。当我们启动 (resume) 协程时,它开始运行函数体并且直到结束或者让出控制权 (yield) ;一个协程只有通过显式调用 yield 函数才会中断。以后,我们可以 resume 它,它将会从它停止的地方继续执行。

它的基本思想非常类似于 Python 的生成器,但有一个关键区别:Lua协程可以在嵌套调用中 yield,而在 Python 中,生成器只能从它的主函数中 yield。在实现上,这意味着每个协程像线程一样必须有独立堆栈。和“平坦”的生成器相比,“带堆栈”的协程发挥了不可思议的强大威力。例如,我们可以在它们的基础上实现一次性延续点 (one-shot continuations)。

对于你做的这些,你如何定义成功?

Luiz:一种语言的成功,取决于使用该语言的程序员数量以及使用它的应用程序的成功。其实,到底有多少人在使用 Lua 编程,我们并没有确切的答案,不过毫无疑问的是,有很多成功使用 Lua 的应用程序,其中包括一些非常成功的游戏。同样地,使用 Lua 的应用程序的范围,从桌面图像处理到嵌入式机器人控制。这表明 Lua 具有一个非常明确的小众市场。最后,Lua 是唯一一种由发展中国家创建并在全球获得广泛应用的语言。它也是 ACM HOPL 唯一重点推介的语言。

Roberto:这很难定义。我曾经在多个领域工作过,在每个领域我从不同的方式在感受了成功。总之,我想说这些的共通之处在于:“被人知晓”。被认可,被公认,被人们推荐,这些都让人非常开心。

对于这门语言,你有什么遗憾吗?

Luiz:我确实没有任何遗憾。不过,事后回想起来,如果我们当初知道我们现在正在做的事情该怎么做的话,这些事情本可以早点完成!

Roberto:我不确信我有什么具体的遗憾,不过语言设计会牵涉到很多困难的决策。对我来说,最困难的决策是在易用性方面。Lua 的目标之一是让非专业程序员易于使用。我没有契合这种定位。因此,当我把自己当作用户,从这个视野来看,有关 Lua 语言的某些决策并非最佳。Lua 的语法就是一个典型的例子:虽然 Lua 的很多应用都得益于其冗长的语法,不过,就我自己的口味而言,我更偏爱紧凑的符号。

你在设计或实现时犯过错吗?

Luiz:我认为我们在设计或实现 Lua 时,并没有犯什么大错。我们学着如何发展一门语言。这绝不仅仅是定义它的语法和语义并将其实现。还有许多重要的社会问题,比如说创建并支持一个社区。这需要通过多种途径,编撰手册、写书、维护网站、邮件列表以及聊天室等。毫无疑问,我们认识到了支持一个社区的价值,明白了做这些工作需要极大的投入,并不亚于在设计和编码工作中的投入。

Roberto:我们很幸运,没有犯什么大错。我们在这个过程中还是出了许多小问题。作为 Lua 演化发展的一部分,我们有机会修正它们。当然,版本间的不兼容问题会让一些用户感到烦恼。好在 Lua 现在已经非常稳定了。

对于成为一名优秀的程序员,你有什么建议?

Luiz:永远不要害怕重新开始,这当然是说到容易做到难。永远不要低估需要注意的细节。你认为未来可能会用到的功能,就不要马上添加了:现在增加这个功能只会让你日后真的需要这个东西时,那些更好的特性很难加入。最后,永远追求更为简洁的解决方案。诚如爱因斯坦所言:尽量简洁,然过犹不及 ( As simple as possible, but not simpler. )。

Roberto:学习新的编程语言,不过一定要读好书!Haskell 是所有程序员都应该学会的一种语言。学习计算机科学:新算法、新形式体系(如果你还不了解,可以看一下 Lambda 演算,或是 pi 演算,CSP 等等)持续改进你的代码。

计算机科学的最大问题是什么?我们又如何教授呢?

Roberto:我想还没有什么能像“计算机科学”那样表达一种广为人知的知识集。并不是说计算机科学不是科学,而是说太难定义什么是计算机科学,什么不是(以及什么重要什么不重要)。计算机科学界的很多人都没有一个正规的计算机科学背景。

Luiz:我把自己当成是一名对计算机在数学中扮演什么角色感兴趣的数学家。当然,我非常喜欢计算机。:)

Roberto:即使是那些有正规计算机科学背景的人,也没有达成共识,我们缺乏一个交流的共同基础。很多人认为是 Java 创建了监视器、虚拟机以及接口(相对于类)等。

是不是有很多计算机科学学科仅仅只是一种职业训练?

Roberto:是的。而且,很多程序员甚至连计算机科学的学位都没有。

Luiz:我并不这么认为,但我不是作为一名程序员被雇用的。从另外一方面来说,我认为,要求程序员有计算机科学学位或是诸如此类的认证是错误的。计算机科学学位并不代表很好的编程能力。很多优秀的程序员也没有计算机科学学位(或许这只在我开始编程时成立;现在我可能是太老了)。我的观点是,一个人拥有计算机科学学位并不能保证他程序写得好。

Roberto:要求所有的专业人士都拥有学位是不对的。但我的意思是这个领域的“文化”太薄弱。几乎没什么东西需要人们必须知道。当然,雇主可以制定自己的要求,但不应该对学位有严格规定。

数学在计算机科学,特别是编程方面,起到一个什么作用?

Luiz:好吧,我是一位数学家。对我来说,数学无处不在。我之所以被编程所吸引,很可能是因为它具有数学的特性:精确、抽象和优雅。编写一个程序有如对一个复杂定理的证明,你可以持续不断地精炼和改进,而且它还能干点实际的事情!

当然,我在编程时根本没想这些,不过我认为,数学的学习对于编程是非常重要的。它有助于带你进入一种特定的心境当中。如果你习惯以抽象事物的自身法则去思考问题,编程就变得更简单。

Roberto:按照 Christos H. Papadimitriou 的说法,“计算机科学是新的数学”。一名程序员如果没有数学功底,就很难有大的作为。从更广的视野来看,数学和编程都具有一些共同的思想原则:抽象。它们都使用同一个关键工具:形式逻辑。优秀的程序员任何时候都在使用“数学”,利用它来确立 code invariants 以及接口模型等。

很多编程语言都是数学家创建的――或许这就是编程困难的原因所在!

Roberto:我会把这个问题留给我们的数学家。

Luiz:好的,此前我已经说过,编程绝对具有数学品质:精确、抽象、优雅。对我来说,设计编程语言就像是构建一种数学理论:你提供了功能强大的工具,其他人可以使用它来做很出色的工作。我一直被那些规模小而功能强的编程语言所吸引。强大的原语和结构之美如同强大的定义和基本理论之美。

你是如何区分出优秀的程序员的呢?

Luiz:你也知道。如今,糟糕的程序员更容易识别――不是因为他们的程序很糟糕(尽管那些程序通常非常复杂又混乱不堪),而是因为你可以感觉到,编程对他们来说并不愉悦,好像他们写的程序对他们自己来说是一个神秘事物,一种负担。

Luiz:我认为调试无法教授,至少不能正式地教授。不过当你跟别人,一个或许比你经验更丰富的人,一起调试的时候,你可以通过具体案例来学习。你可以从他们那里学习调试策略:如何去缩小问题范围,如何去做出预测和评估结果,判断哪些是没有用的,只是些噪音而已。

Roberto:调试本质上是在解决问题。它是一个需要来调动你已学会使用的一切工具的活动。当然存在一些实用的技巧(例如,如有可能,尽量不用调试器,在用 C 这样的底层语言编程时,使用内存检查器),不过,这些技巧只是调试的一小部分。必须像学习编程那样学习调试。

你如何测试和调试你的代码呢?

Luiz:我主要是一块一块的构建,分块测试。我很少使用调试器。即使用调试器,也只是调试 C 代码。我从不用调试器调试 Lua 代码。对于 Lua 来说,在适当的位置放几条打印语句通常就可以胜任了。

Roberto:我差不多也是这样。当我使用调试器时,通常只是用来查找代码在哪里崩溃了。对于 C 代码,有个像 Valgrind 或者 Purify 这样的工具是必要的。

源代码中的注释起到什么作用?

Roberto:用处不大。我通常认为,如果有什么需要注释的,那只是因为程序没写好。对于我来说,一条注释更像是打了个便签,它在说“以后记得重写这段代码”。我认为清晰的代码要比带注释的代码可读性更强。

Luiz:我同意。我一直坚持:注释应该用来表达代码不能清晰表达的东西。

一个项目应该如何文档化呢?

Roberto:强制执行。没有什么工具可以代替一份井井有条、深思熟虑的文档。

Luiz:但是,为一个项目的发展历程写出好的文档,唯一的可能就是从一开始就把这一点放在心上。Lua 并没有这样做;我们从来没想到 Lua 能发展这么快,并在今天获得这么广泛的应用。我们在撰写 HOPL 论文的日子里(这花了将近两年时间!),我们发现已经很难记起当时是怎么做出一些设计决策的了。从另外一个角度来说,如果早期我们要求会议都有正式的会议记录,可能就会失去一些自发性,并错失一些乐趣。

在代码库的发展历程中,你需要权衡哪些因素?

Luiz:我会说“实现的简单性”。这样做的话,速度和正确性随之而来。同时,灵活性也是重点,这样,如果需要,你可以换一个实现方式。

可用的硬件资源如何影响程序员的心态?

Luiz:我是个老家伙了。我是在一台 IBM 370 上学习的编程。要花上几个小时来给卡片穿孔、提交给队列再等到打印输出。我见过各种各样的慢机器。我认为程序员应该体验一下这些机器,因为并不是世界上人人都有最快的机器。编写给大众使用的应用程序的人应该在慢机子上试一下,这样才可以获得更广泛的用户体验。当然,仅可能用最好的机器来开发:把大量时间花在等待完成编译上可一点也不有趣。在现在的全球因特网中,Web 开发者应该尝试慢速连接,而不是他们工作机上的超快连接速度。以平均水平的平台为目标,会让你的产品速度更快、更简单,而且更好。

就Lua来说,“硬件”是指 C 编译器。我们在实现 Lua 的过程中学会的一点就是:以可移植性为目标确实值得。几乎从一开始,我们就是用非常严格的ANSI/ISO C (C89) 来实现 Lua 的。这样一来,Lua 就可以在专用硬件上运行,比如机器人、打印机固件和网络路由器等,这些没有一个是我们当初的实际目标平台。

Roberto:你应该始终认为硬件资源有限,这是一条金科玉律。它们当然总是有限的。“自然厌恶真空”;任何程序都有扩展的趋势,直到它用完了所有的可用资源。此外,随着确定平台上的资源越来越便宜的同时,又会出现一些有严格限制的新平台。微型计算机是这样;移动电话是这样;一切都是这样。如果你想做成市场第一,你最好要时刻关注你的程序需要什么资源。

对于现在或者不久的将来开发计算机系统的人,你在发明、开发和完成你的语言方面,有什么经验可以说的吗?

Luiz:我认为,程序员应该始终记住:并非所有的应用程序都是运行在功能强大的台式机或者笔记本电脑上的。很多应用程序要运行在受限的设备上,比如说手机,甚至是更小的设备等。设计和实现软件工具的人们应该特别关注这个问题,因为没有人会告诉你,你的工具会在什么地方如何使用。因此,就应该为使用最小的资源而设计。你可能会惊奇地发现:很多环境使用了你的工具,而你并没有把这些环境作为主要的应用目标,你甚至都不知道它们的存在。Lua 就碰到过这种事!而且这很自然;我们内部有一个笑话,这其实不是一个真正的笑话:我们讨论在 Lua 中的一个特性的细节时,我们问自己,“好的,不过它会不会在微波炉上运行呢?”

Lua 易于嵌入,而且要求的资源也非常少。你是如何设计的,使得它适应硬件、内存和软件资源都很有限的情况?

Roberto:开始时,我们并没有把这些目标搞得很明确。我们只是为了完成项目才不得已而为之。随着我们的发展,这些目标对我们来说变得更为清晰。现在,我想各方面的主要问题都始终是经济问题。例如,无论什么时候,有人建议一些新的特性,第一个问题就是需要多大的成本。

你有没有因为特性成本太高而拒绝添加它们呢?

Roberto:几乎所有的特性,相对于它们能带给语言的东西来说,都“成本太高”。举一个例子,甚至一个简单的 continue 语句都不符合我们的标准。

添加一个特性需要带来多大的收益才是值得的呢?

Roberto:没有固定的规范,不过看该特性是否能让我们感到“惊喜”是条好的判断标准;也就是说,不仅仅满足其初始其初始动机。这让我想起了另一条经验法则:多少用户会从该特性中受益。某些特性只对一小部分用户是有用的,而其他特性对于几乎所有人都是有用的。

你有例子说明一条新特性对很多人都有用吗?

Roberto:for 循环。我们甚至反对过这个特性,不过当它出现时,它改变了书中所有的例子! 弱表也是出奇地有用。使用它们的人并不多,不过他们应该试试。

在 1.0 版本之后的多年里,你都没有把 for 循环加上。是什么驱使你不加它?而又是什么使你最终加入了它?

Roberto:我们曾无法找到一种让循环通用而简洁的格式,以至于我们一直不肯加入它。当我们发现可以使用一个生成器函数这样一个不错的形式后,我们就把 for 循环加上了。实际上,闭包是使生成器简单通用的要素。因为把生成器函数做成闭包,可以在循环过程中保留其内部状态。

更新代码来获取新特性的优势,重新得到更好的编程实践经验,这些会引起大块费用吗?

Roberto:新特性不是必须使用的。

那么人们会选择一个 Lua 的版本一直用到整个项目的生命期结束,从不升级吗?

Roberto:我认为,在游戏领域大多数人确实是这样做的。而在其他领域,我认为有一些项目不断更新他们所用的 Lua 版本。不过有个反例,魔兽世界从 Lua 5.0 更新到了 5.1 !请留意 Lua 现在要比早年的时候稳定多了。

你们在开发过程中是如何分工的,特别是在编写代码方面?

Luiz:Lua 第一版是由 Waldemar 在 1993 年编码的。自 1995 年左右以来,Roberto 编写和维护了主要代码。我负责一小部分:字节码 dump/undump 模块和独立编译器 luac 。我们一直在修改代码,并通过电子邮件向其他人发送代码修改建议,而且,我们就新特性及其实现开了很长时间的会议。

你从用户那里得到了很多有关语言和实现的反馈吗?对于在语言中加入用户反馈及其修改,你有一个正式的机制吗?

Roberto:我们开玩笑说:你要是忘了什么,那它肯定不重要。Lua 讨论列表非常活跃,不过一些人将开放软件和社区项目等同视之。有一次,我向 Lua 列表发送了以下消息,总结了我们的方法:

Lua 是一款开放软件,不过它从未进行过开放式开发。这并不意味着我们没有听取其他人的意见。实际上,我们几乎阅读了邮件列表中的每一条消息。Lua 里面的若干重要特性就起源或发展至外部的贡献(元表、协程,以及闭包的实现,这里仅举出几个重要的名字),不过,一切都要由我们来最终决定。我们这么做并非觉得我们的判断要比其他人的更好。而仅仅是因为我们想让 Lua 成为我们想要的语言,而不是世界上最流行的语言。

由于采用了这种开发风格,我们不愿意为 Lua 建一个公开的代码仓库。我们不想会我们做的每一处代码修改处处解释。不想为所有的更新保留文档。我们想在有些奇怪的想法时,有足够的自由来试一下,不满意的话就放弃掉,而不需要对每个行动都做一个解释。

为什么你喜欢获得建议和想法,而不是代码?我在想,或许你自己写代码能够让你学到关于问题(解决方案)的更多知识。

Roberto:差不多可以这么说。我们喜欢彻底搞清楚在 Lua 中发生了什么,因此,一段代码贡献不大。一段代码并不能解释为什么采用这种方式,但是,一旦我们理解了它的根本思想,编写代码就成了我们不想错过的乐事。

Luiz:我想对于引入第三方代码还有一个问题,我们无法确保其所有权。我们肯定不想溺死在要别人把代码授权给我们的合法化的过程中。

Lua 会不会达到这种状态:你已经添加了所有想要添加的特性,唯一需要的就是改进实现(例如,LuaJIT)?

Roberto: 我觉得现在就处于这种状态。我们已经添加的特性,即使不算是全部,也是我们想要添加的绝大部分。

你是如何操作冒烟测试和回归测试的?使用开放代码仓库的一大好处是,你可以让人们对几乎每一个修改进行自动测试。

Luiz:Lua 的发布并没有那么频繁,因此,发布一个版本时,已经进行过很多的测试。当这个版本已经相当可靠时我们才发布工作期版本 ( work version / pre-alpha 版),人们能够看中看到新添加的特性。

Roberto:我们确实进行了严格的回归测试。重点在于:因为我们的代码是用 ANSI C 编写的,基本上没有什么可移植性问题。我们没有必要在若干不同的机器上进行测试。一旦修改了代码,我就会执行所有的回归测试,不过这一切都是自动进行的。我要做的只是敲一下 test all 。

如果发现了一个反复出现的问题,到底是局部临时解决,还是全局通盘考虑,你如何判断哪一种是最佳解决方案?

Luiz:我们一直尽量做到一发现 bug 就修复它。不过,因为我们并不经常发布新的 Lua 版本。所以我们都是等到有足够的修复量才发布一个小版本。大版本做的都是改进工作而不是修复 bug 。 如果问题非常复杂(这种情况很罕见),我们会提供一个小版本作临时解决方案。而在下一个大版本中通盘考虑来解决它。

Roberto:通常,局部的权宜修复很快就可以完成。只有在确实不可能进行全局修复时,我们才会作局部的权宜方案。例如,如果某个全局修改需要一个新的不兼容接口。

从开始到现在,已经过去了这么多年,你仍然会为有限的资源而设计吗?

Roberto:当然会的,我们一直致力于此。我们甚至考虑过改变 C 结构内的字段顺序,以节省几个字节。:)

Luiz:相比于以前,现在有更多的人们把 Lua 语言运用到比以前更小的设备上面。

以用户视野来对简单性的追求怎样影响语言设计的?我想起了 Lua 对类的支持,让我想起了许多在 C 中实现面向对象的方式(不过没那么另人烦恼)。

Roberto:目前,我们有一个准则叫“机制而非法策”。它可以保证语言简洁,不过就像你说的,用户必须提供它自己的法则。就类这个问题来说,有很多方法实现它。有些用户会喜欢某种方式,而其他用户则可能痛恨它。

Tcl 也用了一种类似的方法,不过各家各有其法使它支离破碎。因为 Lua 有特定的目的,所以分裂对它不是啥严重问题吗?

Roberto: 对。有时这是个问题。但对于大量应用(比如说游戏)来说,这不是个问题。Lua 主要用来嵌入到别的应用程序中。而应用程序会提供一个坚固的框架来统一编程规范。你看到了 Lua/Lightroom, Lua/WoW, Lua/Wireshark ―― 这个每个都有自己的内部文化。

你认为 Lua 这种“我们提供机制” 的展延性风格,给人带来巨大的好处吗?

Roberto:这么说并不确切。对于大多数事情来说,它是一种折衷处理。有时候,提供即刻可用的规范法则非常有用。“我们提供机制”更为灵活,但需要做更多的工作,并使得风格分裂。这最终也是个经济问题。

Luiz:另一方面,有时候这很难向用户解释。我的意思是,让他们理解是这些机制是什么,以及这些机制的原理。

这会使项目之间交流代码变得困难吗?

Roberto:没错,通常就是这样。它也阻碍了独立库的发展。例如,WoW 拥有大量的库(甚至连用遗传算法解决货郎担问题的库都有),不过在 WoW 之外却没人去用它们。

Luiz:我们并不担心:语言还保持相同,只是可用的函数不同而已。我认为这些应用程序会在某些方面受益于此。

严肃的 Lua 用户会在 Lua 基础上编写他们自己的方言吗?

Roberto:很有可能。至少我们还没有宏。要是有宏的话,我认为你可以使用宏来创建一种真正的方言。

Luiz: 本质上还不算一种语言的方言。不过算是用函数来实现的一种特定领域语言。这曾是 Lua 的设计目的之一。当 Lua 仅仅用来作数据文件时,它看起来是一种方言,当然那些只是 Lua 表而已。有些项目或多或少实现了一些宏。比如我想起了 metalua 。这也是 Lisp 的一个问题。

你为何选择提供一种可扩充的语义?

Roberto:它开始是作为提供面向对象特性的一个方法。我们不想在 Lua 中添加 OO 机制, 但用户想要这些。我们想到这个方法,提供足够的机制让用户实现自己的 OO 机制。到现在我们也觉得这是一个正确的决策。然而,这使得用 Lua 的方式 OO 编程对于初学者来说更为困难。但它也给语言带来了大量的灵活度。特别是,当我们把 Lua 和其它语言混用(这是 Lua 的一个特色)时,这种灵活度使得程序员可以让 Lua 的对象模型去适应外部语言的对象模型。

目前的硬件、软件、服务和网络环境同你最初设计时的系统环境有何不同?这些变化对你的系统以及未来的改变有何影响?

Roberto:因为 Lua 是以极高的可移植性为目标,我认为目前的“环境”同以前的环境并没有什么不同。例如,我们开始开发 Lua 时,DOS/Windows 3 跑在 16 位机器上;一些老机器仍然是 8 位的。目前我们没有 16 位的台式机了,不过,若干使用 Lua 的平台(嵌入式系统)仍然是 16 位或者甚至是8位的。

最大的变化在于 C 语言。回头看 1993 年,当时我们刚开始做 Lua ,ISO (ANSI) C 还没有像今天这么成熟。很多平台仍然使用 K&R C 。很多应用程序写了一些很复杂的宏来使得程序通过 K&R C 和 ANSI C 两者的编译。主要的区别在函数头的声明。当时,坚持使用 ANSI C 是一个冒险的决定。

Luiz:我们仍未感觉到有必要转移到 C99 上面。Lua 是用 C89 实现的。如果过渡到 64 位机器上时出现些小毛病的话,或许我们必须使用 C99 的一部分(特别跟长度有关的类型定义),不过我并不希望出现任何问题。

如果能全部重新构建 Lua 的 VM 的话,你仍然会坚持使用 ANSI C 吗,或者你希望有一个更好的语言用于跨平台的底层开发?

Roberto:不。ANSI C 是我(目前)知道的可移植性最好的语言。

Luiz:有些杰出的ANSI C编译器,不过,即使是使用它们的扩展,也不会给我们带来很多性能提升。

Roberto:改进 ANSI C 并保持它的可移植性和性能并不容易。

顺便问一句,你是说 C89/90 吗?

Luiz:再者,我不确定 C99 能给我们带来很多额外的特性。我还特别想到了 gcc 中使用的带标签的 goto 语句作为 switch 的一种替代方案(在虚拟机执行的主干里)。

Roberto:在很多机器中,这样做可以改进性能。

Luiz:我们早期对它作过测试,最近也有人也对它进行了测试,效果并不吸引人。

Roberto:部分原因在于我们基于寄存器的体系结构。它倾向于用较少的操作码,每个操作码分担更多的工作。这减少了分发器的负担。

你为什么要构建一个基于寄存器的 VM 呢?

Roberto:为了避免所有的 getlocal/setlocal 指令。我们也想去实践一下我们的想法。我们想啊,如果它运行得不好,至少我们还能写一些研究这个的论文。而最后,它运行得非常好,而我们也只写了一篇论文。:D

在 VM 上运行对调试有没有帮助?

Roberto:它没有提供“帮助”;它改变了整个调试的概念。既调试过编译型语言,又调试过解释型语言(比如 C 和 Java)的人都知道它们天差地别。好的VM 会让语言变得更安全,在某种意义上,该错误可以从语言层面上理解,而非机器层面(比如说段错误)。

如果语言是平台无关的,这对调试有何影响?

Roberto:通常它有利于调试,因为一种语言越是和平台无关,它就越需要可靠的抽象描述和行为。

考虑到我们是人,而人总会犯错。你是否曾经考虑过:为了在调试阶段有所帮助,需要向语言添加某种特性或是从中删除一些特性?

Roberto:当然了。辅助调试的第一步就是良好的错误消息。

Luiz:从初期版本开始,Lua 中的错误消息就在一直改进。我们已经从可怕的“调用操作对象不是一个函数”的错误消息(这条错误消息一直用到 Lua 3.2),变成了更好的错误消息:“试图调用全局 'f' (一个 nil 值)”。从 Lua 5.0 开始,我们使用对字节码的符号追踪 (Symbolic execution) 来试着提供更有用的错误消息。

Roberto:在语言自身的设计中,我们一直设法避免使用复杂的结构。如果它很难理解,就会更难调试。

在设计一门语言和设计用这种语言编写的程序之间,有什么联系?

Roberto:至少对我来说,设计一门语言的要点在于从用户的角度出发,也就是说,去考虑用户将怎样使用每一个特性,用户将会如何将这些特性和其它语言对比。程序员总会找到使用一种语言的新方式,优秀的语言应该允许那些意想不到的使用方法。不过,语言的“正常”用法应该遵从语言设计者的初衷。

语言的实现会在多大程度上影响语言的设计?

Roberto:这是一条双向道。实现会对语言产生巨大的影响:我们不应该设计无法高效实现的东西。一些人忘了这点。在设计任何软件时,效率一直是一个(或者是惟一的)主要约束条件。不过,设计也可能会对实现产生较大的影响。一眼看去,Lua 的几个特色之处都来自于它的实现(体积小、优秀的 C API ,以及可移植性),而 Lua 的设计在使这些实现变得可能中,起到了关键作用。

Roberto:Lua 第一版使用了 lex 和 yacc 。不过,Lua 最初的主要目标之一是作为一种数据描述语言,和 XML 没什么不同。

Luiz:但是时间要更早一些。

Roberto:很快人们开始把 Lua 用于数兆字节的数据文件,此时 lex 生成的扫描器迅速变成了瓶颈。手写一个优秀的扫描器非常容易。而且只做了这么一点简单的改进后,我们就提高了 Lua 大约 30% 的性能。

决定从 yacc 改成手工编写解析器是很后来的事情,这个决定做得并不容易。这起源于几乎所有 yacc/bison 实现使用的主干代码的问题。

当时,它们的可移植性很差(例如,用了好多处的 malloc.h ,这是一个非 ANSI C 的头文件),而且,我们无法控制其整体质量(例如,控制堆栈溢出和内存分配错误等问题),而且它们也不是可重入的(比如要在解析代码的过程中调用解析器)。另一方面,如果你想要像 Lua 那样及时生成代码,自底向上解析器也不如自顶向下的那么好。因为它难以处理“继承属性(Inherited attributes)”。我们改写之后,发现我们手写的解析器要比 yacc 生成的那个略小以及略快一点。不过这不是改写的主要原因。

Luiz:自顶向下分析器还能提供更好的错误消息。

Roberto:不过,我从不推荐为没有成熟语法的语言手写解析器。并可以肯定LR(1)(或是 LALR 甚至 SRL)会比 LL(1) 强大多了。甚至对于 Lua 这样的简单语法的语言来说,我们也必须使用一些技巧来构建一个像样的分析器。例如,处理二元表达式的程序并没有按原始语法去处理,而是用了一个聪明的基于优先级(priority-based)的递归方案。在我的编译器课上一直向我的学生推荐

你的教学生涯中有什么趣闻轶事吗?

Roberto:我刚开始教授编程时,供我们的学生使用的计算机设备是一台大型机。有一次,一个非常优秀的团队提交的一个程序作业,居然连编译都没通过。我找他们来谈话,他们发誓用好几个测试案例仔细的测试了程序。当然了,他们和我用的是同一台机器,完全相同的环境,都是在那台大型机上。这个神秘事件只到几周后才搞明白。原来机器上的 Pascal 编译器被升级了。升级刚好发生在学生完成任务和我开始批改作业之间。他们的程序有一个很小的词法错误(如果记得没错,是多了个分号),而老的编译器没有检测到!

程序设计基础(C语言)课程主页-2016级 数据结构 数据结构课程主页-2015级() 数据结构课程主页-2014级() (CSDN学院)数据结构基础系列网络课程主页 C++程序设计(新版) C++程序设计-2015级主页...

网上看到的超牛c语言学习

一本学习教程,现在C语言教材多如牛毛,但推荐大家使用《C语言程序设计》谭浩强主编 第二版 清华大学出版社,此书编写的很适合初学者,并且内容也很精到。除此以外,现在有很多辅助学习的软件,毕竟现在是Window...

C语言程序设计》一 第 1 章 程序设计概述

使读者直观地建立起对程序、程序设计程序设计语言的基本认识,接着通过两个简单的C语言程序来了解C语言程序概貌,然后介绍结构化程序设计实现问题的求解过程,最后通过实例学习结构化程序设计的基本方法。...

《21天学通C语言(第7版)》一导读

21天学通C语言(第7版)从书名便可看出,通过学习本书,你可以自学C程序设计语言。在众多语言(如C++、JAVA和C#)中,C仍然是学习程序设计语言的首选。第1课中将详细介绍其中的原因。选择C作为程序设计语言是明智之...

C语言编程——零基础初学者指南(第3版)》一导读

第1章 什么是C程序设计 1.1 什么是程序 1.2 编写C语言程序需要什么 1.3 编程过程 1.4 使用C语言 第2章 编写第一个C程序 2.1 概述 2.2 main()函数 2.3 数据的种类 2.4 第二个程序示例 3 代码的注释4 在屏幕...

C语言程序设计》一导读

第1章 程序设计概述 1.1 程序和程序设计语言 1.2 简单的C语言程序 1.3 实现问题的求解过程 1.4 案例学习——计算三角形的面积 第2章 C程序设计基础 2.1 案例引入——计算三角形的面积 2.2 常量、数据的存储...

带你读《C语言程序设计习题解析与上机指导》之一:...

C语言程序设计是一门实践性很强的课程,该课程的学习有其自身的特点,学习者必须通过大量的编程训练,在实践中掌握程序设计语言,培养程序设计的基本能力,并逐步理解和掌握程序设计的思想和方法。具体地说,通过...

C语言编程——零基础初学者指南(第3版)》一1.4 ...

别急,在第2章中,我们会一步一步地教你如何编写C语言程序。本章小结 本章介绍了C程序设计语言,帮助读者选择一个编译器编辑、调试和运行程序。要牢记以下几点。首先要在计算机上安装一个C编译器。准备好学习C程序...

带你读《C语言程序设计教程 第4版》之一:C语言概述

高等院校精品课程系列教材点击查看第二章点击查看第三章C语言程序设计教程第4版 朱鸣华 罗晓芳 董 明 孟 军 汪德刚 编著第1章 C语言概述 1.1程序设计的基本概念 计算机的产生是20世纪重大的科技成果之一。...

C语言程序设计与实践(第2版)》——1.3 C语言学习...

本节书摘来自华章出版社《C语言程序设计与实践(第2版)》一书中的第1章,第1.3节,作者:凌云等著,更多章节内容可以访问云栖社区“华章计算机”公众号查看 1.3 C语言学习与自然语言学习的关系 C语言相对来说是...

我要回帖

更多关于 C源程序的基本单位是 的文章

 

随机推荐