深度好文:How to get started in C++!

 Datawhale干货 

作者:zclll,推荐:卢雨畋,Datawhale成员

在大家的不懈催更下(hhh),这篇文章终于和大家见面了。对于程序设计、软件开发而言,我都只是一个入门水平而已(也许C++略多一点,但也并不很多)。对任何知识的掌握,都不足以保证完美无缺。那么有什么可以和大家说的呢?我想,只有对一些事物的理解与认知。如何快速地养成一位普通的C++工程师,是培训班应该去做的任务。而我们,应该为成为一名优秀的软件工程师打好基础

这些内容显然不会是无上真理,因为它受限于笔者的有限认知。但至少,做到这些内容,成为比笔者更优秀的C++工程师,还是非常容易的。

最重要的

对于学编程的新手来说,最重要的事情是——先把代码写起来。这件事情对于各个行业来说都很重要,但也许对coding尤为重要。如果说程序员这个行业一定需要一些天分的话,那么最重要的一个,就是有“先把事情搞起来”的执行力。

这意味着,也许你还在犹豫“我应该学什么,用哪本书、学谁的课”,什么时候学,要不要沐浴更衣,要不要择良辰吉日——都不必。就用你手头有的资源,写你想到的东西,先写起来。如果没有合适的,那么在这篇文章中,我会提供一些——但它不会是本篇文章的重点。

C++需要学什么

被问最多的问题是:“有没有推荐的学习路线?”

要解答这个问题,必须要解决几个前置问题:

你得明确,学C++是为了什么为了赶紧找一份不错的工作?为了做有价值的东西?为了做很酷的东西让自己开心?

自己要去做哪个方面的开发是hpc,服务端,嵌入式,量化……还是什么?让我们一个一个来说。

第一点,你的动机决定了你要去做什么。因为人的精力是有限的,你不可能对C++这么辽阔的版图面面俱到。特别是,C++的世界中有一类特别的人——语言律师,他们对复杂的语法规则——你绝不可能了解的那些——如数家珍,但不一定是非常优秀的软件工程师。你需要决定自己要不要成为这样的人。这并没有优劣之分,只是不同的选择。

就像“茴字有几种写法”一样。当然需要有人知道(比如计算机史学家),但如果你希望成为一名优秀的coder的话,显然不是你需要知道的事情。

那么,掌握一门语言需要到什么程度呢?

对语言的掌握

C++是一门非常复杂的语言,与其他语言最大的不同在于,它有一套被称之为“标准”的东西3 。这套标准非常详尽,事无巨细地规定了C++世界中的每一个角落该长什么样子。也正因为如此,C++世界中有一个经典的笑话,关于 “面试者在简历上写了精通C++结果面试官出了一堆元编程打击得面试者无地自容的那些事儿”。

这意味着我们永远都不能够说自己“精通C++”了么?我看并不是。

很多群友都知道,我在自己的简历上的确写了“精通C++”。因为在我看来,自己已经完全满足这个要求了。我能保证做出cpp quiz中的hard题吗?我不能。给我几个超复杂的候选函数,我能搞懂重载决议会pick哪个吗?我不能。

但让这些炫技的东西出现在一个正经项目的代码里,比“不会C++”可怕一万倍。

我能保证的是,对于我们的项目中所涉及到以及可能涉及到的C++知识,我已经掌握了至少99%了。我的精力应该放到如何写出更简洁、低耦合、可复用的代码上去,应该研究如何让我的代码对CPU、缓存更加友好,应该研究各个组件的架构应该如何设计才能从宏观角度提高性能和稳定性……

——而不是让那些超复杂且有害的东西来占据我本就有限的时间

如果一个机制需要非常复杂的语法去实现它,那么相信我,一定有更好的办法。如果你在cr的时候看到了一个完全看不懂的写法,那么相信我,give it a -1.

这是第一点:你必须清楚自己学C++是为了什么。花一些时间做cool的事情来取悦自己没什么不好,但长期来看,你得大体坚持在正确的道路上,才能写出真正很酷的东西。它会是一个从性能、软件工程等角度上都很酷的东西,而并不因为你采取了很先进或很复杂的语法。

先进的语法只有在让代码更简单的时候才是真的先进;复杂的代码只有让更多的代码更简单/性能更高的时候才是合适的复杂。

所以——

比语言更重要的

专业能力

语言永远只是一个工具。

我想大多数人学C++的目的,是因为它容易写出可掌控、高性能的代码,这就意味着语言不是第一性的,它是为了功能而服务。

如果对业务确实有利,那么明天去写Rust又怎么样呢?任何人都不应该抵触在合适的时机选择更合适的语言。C++也许的确更难学,但不要因为学了它而拥有可悲的壁垒。你的领域知识远比语言更重要

所以,你应该去考虑:我要成为一名hpc开发?还是嵌入式开发?还是后端开发?……

只不过你喜欢C++,那么你可以选一些用C++比较频繁的领域,把C++作为你的核心竞争力之一,而不是“一棵树上吊死”。

当然,语言是这一切的基础。所以你首先得把C++学到一个“能写出各类功能代码”的程度,然后就要尽快让领域知识驱动着你去学习和coding。而这并不难。我们预期花多少时间呢?如果你慢慢学,每天学一点,两三个月总够了;如果你把这作为你的主业,那么顶多需要半个月~一个月的时间。

学习方式

关键点就在于,不要把做一件实事的bar想象地太高

最基本的语法都掌握了吗?很好,去学你想要做的那个领域的知识吧!

思考能力

抓住问题的本质

我想,选择C++作为主要语言的人,往往都对高性能有一些执念吧?那么“抓住问题的本质”这件事情简直太重要,太重要了!

比如有些acmer第一次接触到编译期计算,会想到——我靠,那我直接把运行时算的东西挪到编译期不就得了?TLE再也与我无关!

——当然我们知道这是天方夜谭。但我看到过不少人曾有过这样的想法。问题不在于他们没有掌握元编程的正确知识,而是他们未能建立正确的思考模型

比如说, 下列代码段中,虚函数调用是不是必须的?

struct A{ virtual void f(){ ... } };
struct B : A { void f() override { ... } };

{
     A* a = new B{};
     a->f();
}

如果你C++学得多一点,可能会很快反应过来:我可以用CRTP优化掉这个虚调用嘛!

但这件事的本质并不是CRTP,这件事的本质是“运行前就已经有足够的信息”,即使没有CRTP, 也会有ARTP, BRTP,只要是一个图灵完备的语言,就总有技巧消除掉这个开销。

对你来说,最重要的东西不是学会用CRTP,而是“知道什么是理论上可以实现的”。

就像网络工程师常用的“零拷贝优化”一样,即使没有人总结出来这种优化,它也应该是你有能力在业务优化中思考出来的。你不是掌握了一堆兵器,然后在项目上挨个按上去,看看卡不卡得上。而是:如果这个地方理论上可以更好,那么我总能有个技术把它搞了。

我认为, 从这个角度——也就是事物的本质分析问题的能力, 比编程能力要重要很多倍。掌握很多技术可能能让你成为一名还算不错的软件工程师,而掌握从本质角度思考问题的能力,你才有可能成为一名架构师。

在恰当的抽象层上思考问题

这也是非常重要的一种能力,同时也是对软件工程的理解中的一环。就像我们经常说到,XXX对程序员来说是透明的。CPU有乱序发射,编译器有各种优化,这意味着你需要考虑这些东西会不会搞砸你的代码吗?No。所有的优化与变换,首先得是透明的、条件保持的

如果你决定了用 swap 去交换对象,就不用担心它是否因为应用了针对PIMPL的优化、针对POD的优化,而搞砸你的代码。

如果你需要用或者提供一个接口,你所需要关注的全部就是接口的前条件和后条件,不要做跨层的思考。(除非你正打算从数据流的角度优化算法)

成长路径

原则性的(也是最重要的)东西讲完了,让我们来讲讲成长路径。所谓“成长路径”,简单讲就是“我在各个阶段应该学些什么?”,说直白点,就是“我应该往简历上写些什么?”。

首先第一部分,是你的基础能力,这主要是语言能力——毕竟你得先把想到的东西写出来,才有别的东西可谈。

所以,开始把代码写起来吧。

书籍、参考与练习

对于入门来说,有很多书籍是不错的,但它们都比不上找到自己想做的东西,然后一边写一边查。当然,参考书也是有必要的,我会推荐这本:

《CPP Primer Plus》 

这本书的质量据说不错,但我没看过。叶神说过相比之下还不错,我信叶神的(但他好像也没看过)。

然后呢?把这本书(或者其他你觉得不错的)看个差不多,就已经足够去写代码了。现在对你来说最重要的事情,就是找到你的目标领域当中适合入门的toy,写起来。

请坚决摒弃上学时期张嘴等老师“喂饭”的思维,真正的学习绝不是把书一本一本的读下去就有效果的!

在这个过程中,你需要同步提高自己的C++水平,那么有这两本非常不错的书

  • 《现代C++32讲》

  • 《Effective Modern C++》

后者我更推荐互联网上的gitbook版本,看着更舒服。

如果你需要检验一下自己对C++语法的掌握如何,那么你可以在这里找到很多练习:

Cpp Quiz 

请注意,不要痴迷。你不需要花费时间在这里的Hard难度上。

而如果你需要确定地查找一个关于C++语法与库的正确答案,请到这个对于Cpper最重要的网站:

cppreference.com 

这时候,你就开始需要接触一些C++的最佳实践了:

StackOverflow —— 这个地球上最高质量的程序员问答平台之一 

CppCon —— C++的前沿发展汇报 

总结来说,对于绝大多数新生代的C++ coder(或是其他门类的软件工程师)来说,在学习过程中最需要树立的一个观点是:绝对不是已经掌握了对应的技能才去做东西,你要做一个东西,正是因为你还不会它。

当然,这时候,写出更好的代码就已经不能只局限于使用更合适的语法了,而是必须要从软件工程的角度重新思考你的代码。

重构

最好的方法,就是定期重构自己的代码。重新去思考能不能用更好的抽象,能不能更好地复用?更好的解耦合?

隔一段时间回头看,总会觉得自己的代码像依托答辩,那么,这就是你提升的时候了!

请不要忘记……

能够实际做一些东西,总比纸上谈兵要好,不是吗?

所以一定不要忘了,学会Linux、Cmake、gtest、、git、perf、valgrind、benchmark……这些工具的使用!

那么,我该怎么去学它们呢?在什么时机去接触呢?

答案是当你意识到你需要的时候。 请再次提醒自己,写代码已经不是中学时期等老师来喂你知识的时候了,你必须自己进行思考当你思考如何改进自己的代码的时候,这些工具就会自然进入到你的世界。——这种学习的过程,将会伴随你的整个职业生涯。

如果你说,我写了很多代码(xcpc coding除外),但是仍然好像没有接触到这些工具的使用?那么,这就是你的问题了,其他人无能为力。的确有的人不具备主动思考和学习的能力,那么你要做的应该是关闭这篇文章,去找个培训班让他们想办法帮你物色一个好的工作。

项目

哦,到了大家(中的很多人)最为关心的部分了!所以我把这一小项提升为一个章节来说——

其实人的眼界和机遇,比我们想象当中重要太多了对于绝大多数人来说,无论你找到的工作是什么样的,这个世界上几乎一定存在着更轻松、待遇更优渥的另一个,只是你无法发现。所以多去拓展消息渠道,多接触更优秀的人,对你的意义比想象中的大。至少对我来说,如果大一就能有这样一个QQ群的话,我想自己一定能成长到另一个高度。

所以,好好利用这些资源,去决定自己要做什么。

还是那句话,领域知识才是根本。你要成为一名后端/前端/嵌入式/区块链……工程师,而不是“C++工程师”。

名校Lab

如果说接触商业项目相对困难的话,那么去做lab(课程附带实验)很容易成为你写实际代码的第一步!

这些国外顶尖名校的lab与我们普通大学的实验课有天壤之别,它们往往要求你完成一个非常系统且颇具难度的软件,这其中覆盖了几乎全部这个课程的关键基础知识。因此,它们很适合作为新手入门的第一步。例如以下Lab:

  • CMU 15-445 数据库

  • CMU 15-721 数据库系统进阶

  • MIT 6.824 分布式系统

  • MIT 6.828/s.081 操作系统

  • MIT 6.S191 深度学习

  • ……

当然,随着互联网卷度增加,lab也有“泛滥”之嫌。但相信我,如果你真的搞懂了一个lab,它已经足以让你在对应领域成为一名合格的(校招)工程师了。

开源项目

实际参与开源项目的含金量,我认为不需要我多说了。它远超你的Lab、xcpc牌子、绩点、奖学金……

这对你来说会不会很遥远?我可以告诉你的是,完全不。开源社区中的人们,大多是一群非常友好的家伙,而且非常多的项目都有一些容易让新人上手的case,而且会有很友好的mentor去对应的协助你完成。

所以,你只需要摆脱胆怯的情绪,就可以很容易地去参与其中。例如1我们的大型分布式数据库DORIS,就给新手提供了非常友好的Good First Issues栏目,你可以非常容易地选择一个自己想要去完成的内容,然后接受导师的指导。

之后你会发现,原来参与一个开源项目并不是那么困难嘛~

作者网址:https://zclll.com/

d401a562584e1375bfe0bd61a124150b.png

整理不易,三连

Bug Description Before I start: I know that the author of Dibbler considers this software unmaintained (https://klub.com.pl/dhcpv6/); yet it is included in the repositories, so here I am with this bug report. == Issue #1: Dibbler binds to incorrect link-local address of a ppp(oe) interface == After upgrading my home router from Ubuntu 20.04 to Ubuntu 22.04, I have noticed that dibbler-client is no longer capable of starting up and binding to the PPPoE (PPP) interface created as part of my connection set-up process. This is how the link local addresses look like: rzajic@router:/home/work/dibbler/dibbler-1.0.1$ ip -6 a s dev ppp999 scope link 28: ppp999: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1492 qdisc cake state UNKNOWN group default qlen 3 inet6 fe80::8157:b42e:45ec:96ae peer fe80::427c:7dff:fe93:d231/128 scope link valid_lft forever preferred_lft forever fe80::8157:b42e:45ec:96ae is my side of the point to point link fe80::427c:7dff:fe93:d231 is the ISP side of the point to point link (The addresses change on every reconnection, it's nothing sensitive.) The error logs from Dibbler-client were: Apr 25 11:00:39 router.local systemd[1]: Starting LSB: Starts DHCPv6 client... Apr 25 11:00:39 router.local dibbler-client[86944]: Starting DHCPv6 client: dibbler-client. Apr 25 11:00:39 router.local systemd[1]: Started LSB: Starts DHCPv6 client. Apr 25 11:00:39 router.local DibblerClient[86950]: PD: Following interfaces marked as downlink: br0.2 Apr 25 11:00:39 router.local DibblerClient[86950]: Prefix delegation option (no parameters) found. Apr 25 11:00:39 router.local DibblerClient[86950]: Parsing /etc/dibbler/client.conf done, result=0(success) Apr 25 11:00:39 router.local DibblerClient[86950]: 1 interface(s) specified in /etc/dibbler/client.conf Apr 25 11:00:39 router.local DibblerClient[86950]: Interface ppp999/28 configuration has been loaded. Apr 25 11:00:39 router.local DibblerClient[86950]: DUID's value = 00:01:00:01:27:3c:48:99:40:8d:5c:b3:2a:ab was loaded from client-duid file. Apr 25 11:00:39 router.local DibblerClient[86950]: My DUID is 00:01:00:01:27:3c:48:99:40:8d:5c:b3:2a:ab. Apr 25 11:00:39 router.local DibblerClient[86950]: Loading old address database (client-AddrMgr.xml), using built-in routines. Apr 25 11:00:39 router.local DibblerClient[86950]: Unable to open client-AddrMgr.xml. Apr 25 11:00:39 router.local DibblerClient[86950]: Bind reuse enabled (multiple instances allowed). Apr 25 11:00:39 router.local DibblerClient[86950]: Creating control (::) socket on the lo/1 interface. Apr 25 11:00:39 router.local DibblerClient[86950]: Creating socket (addr=fe80::427c:7dff:fe93:d231) on ppp999/28 interface. Apr 25 11:00:39 router.local DibblerClient[86950]: Unable to bind socket (iface=ppp999/28, addr=fe80::427c:7dff:fe93:d231, port=546). Apr 25 11:00:39 router.local DibblerClient[86950]: Low-level layer error message: Unable to bind socket: Cannot assign requested address Apr 25 11:00:39 router.local DibblerClient[86950]: Socket creation (addr=fe80::427c:7dff:fe93:d231) on ppp999/28 interface failed. Apr 25 11:00:39 router.local DibblerClient[86950]: Fatal error during TransMgr initialization. After a quick review, it became clear that the dibbler-client attempts to bind to incorrect IPv6 link local address. This might be related to how the new(er?) clients present the link local addresses. Using the script from https://raw.githubusercontent.com/d0u9/examples/master/C/netlink/ip_show.c, I can see that the link local address on my side of the point to point link is now represented as "local address", while on non-point to point links the link local addresses are represented as interface addresses. ================================== family: 6 dev: ppp999 prefix length: 128 local address: fe80::8157:b42e:45ec:96ae (flags 128, scope 253) if address: fe80::427c:7dff:fe93:d231 (flags 128, scope 253) ================================== family: 6 dev: tap4 prefix length: 64 if address: fe80::4048:1aff:fe2e:64f4 (flags 128, scope 253) This can be worked around by modifying the low level Linux code (note that there seems to be a bug in Dibbler code, which uses IFLA_ADDRESS [belongs to layer 2, e.g. MAC address] instead of IFA_ADDRESS): --- lowlevel-linux.c 2022-04-25 23:56:08.736090768 +0200 +++ dibbler-1.0.1/Port-linux/lowlevel-linux.c 2022-04-25 23:30:35.889052974 +0200 @@ -277,7 +277,7 @@ if (!rta_tb[IFA_LOCAL]) rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS]; if (!rta_tb[IFA_ADDRESS]) rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL]; - memcpy(addr,(char*)RTA_DATA(rta_tb[IFLA_ADDRESS]),16); + memcpy(addr,(char*)RTA_DATA(rta_tb[IFA_LOCAL]),16); if (addr[0]!=0xfe || addr[1]!=0x80) { continue; /* ignore non link-scoped addrs */ } The local address is always set a few lines above the memcpy() call. After rebuild (see Issue #3 below), the process starts, binds and request a prefix (which was my goal at the beginning): Apr 25 23:39:45 router.local systemd[1]: Starting LSB: Starts DHCPv6 client... Apr 25 23:39:45 router.local dibbler-client[243724]: Starting DHCPv6 client: dibbler-client. Apr 25 23:39:45 router.local systemd[1]: Started LSB: Starts DHCPv6 client. Apr 25 23:39:45 router.local DibblerClient[243730]: PD: Following interfaces marked as downlink: br0.2 Apr 25 23:39:45 router.local DibblerClient[243730]: Prefix delegation option (no parameters) found. Apr 25 23:39:45 router.local DibblerClient[243730]: Parsing /etc/dibbler/client.conf done, result=0(success) Apr 25 23:39:45 router.local DibblerClient[243730]: 1 interface(s) specified in /etc/dibbler/client.conf Apr 25 23:39:45 router.local DibblerClient[243730]: Interface ppp999/28 configuration has been loaded. Apr 25 23:39:45 router.local DibblerClient[243730]: DUID's value = 00:01:00:01:27:3c:48:99:40:8d:5c:b3:2a:ab was loaded from client-duid file. Apr 25 23:39:45 router.local DibblerClient[243730]: My DUID is 00:01:00:01:27:3c:48:99:40:8d:5c:b3:2a:ab. Apr 25 23:39:45 router.local DibblerClient[243730]: Loading old address database (client-AddrMgr.xml), using built-in routines. Apr 25 23:39:45 router.local DibblerClient[243730]: DB timestamp:1650922785, now()=1650922785, db is 0 second(s) old. Apr 25 23:39:45 router.local DibblerClient[243730]: Auth: Replay detection value loaded 0 Apr 25 23:39:45 router.local DibblerClient[243730]: All client's 00:01:00:01:27:3c:48:99:40:8d:5c:b3:2a:ab leases are not valid. Apr 25 23:39:45 router.local DibblerClient[243730]: Bind reuse enabled (multiple instances allowed). Apr 25 23:39:45 router.local DibblerClient[243730]: Creating control (::) socket on the lo/1 interface. Apr 25 23:39:45 router.local DibblerClient[243730]: DEBUG: realIface: <IfaceIface name="ppp999" ifindex="28" hwType="512" flags="0x110d1" mBit="0" oBit="0"> <!-- no-loopback running multicast --> <!-- PrefixLength configured to 128 --> <!-- 1 link scoped addrs --> <Addr>fe80::8157:b42e:45ec:96ae</Addr> <!-- 3 non-local addrs --> 2001:1ae9:ff2:xxxx::1 2001:1ae9:ff2:xxxx:faf0:100c:edd5:ed6d 2001:1ae9:ff2:xxxx:8157:b42e:45ec:96ae <Mac></Mac> </IfaceIface> , Interface Apr 25 23:39:45 router.local DibblerClient[243730]: Creating socket (addr=fe80::8157:b42e:45ec:96ae) on ppp999/28 interface. Apr 25 23:39:45 router.local DibblerClient[243730]: Initialising link-state detection for interfaces: ppp999/28 Apr 25 23:39:45 router.local DibblerClient[243730]: CONFIRM support compiled in. Apr 25 23:39:45 router.local DibblerClient[243730]: Creating SOLICIT message with 0 IA(s), no TA and 1 PD(s) on ppp999/28 interface. Apr 25 23:39:45 router.local DibblerClient[243730]: Sending SOLICIT(opts:1 25 8 ) on ppp999/28 to multicast. Apr 25 23:39:45 router.local DibblerClient[243730]: Sleeping for 1 second(s). Apr 25 23:39:45 router.local DibblerClient[243730]: Received 81 bytes on interface ppp999/28 (socket=3, addr=fe80::427c:7dff:fe93:d231). Apr 25 23:39:45 router.local DibblerClient[243730]: Received ADVERTISE on ppp999/28,trans-id=0x655732, 3 opts: 2 1 25 Apr 25 23:39:45 router.local DibblerClient[243730]: Script execution skipped for ADVERTISE response to SOLICIT. No action needed for this message type. Apr 25 23:39:45 router.local DibblerClient[243730]: Sleeping for 1 second(s). Apr 25 23:39:46 router.local DibblerClient[243730]: Processing msg (SOLICIT,transID=0x655732,opts: 1 25 8) Apr 25 23:39:46 router.local DibblerClient[243730]: Creating REQUEST. Backup server list contains 1 server(s). Apr 25 23:39:46 router.local DibblerClient[243730]: Advertise from Server ID=00:03:00:01:40:7c:7d:93:d2:31, no preference option, assumed 0.[using this] Apr 25 23:39:46 router.local DibblerClient[243730]: Sending REQUEST(opts:1 25 2 8 ) on ppp999/28 to multicast. Apr 25 23:39:46 router.local DibblerClient[243730]: Sleeping for 1 second(s). Apr 25 23:39:46 router.local DibblerClient[243730]: Received 81 bytes on interface ppp999/28 (socket=3, addr=fe80::427c:7dff:fe93:d231). Apr 25 23:39:46 router.local DibblerClient[243730]: Received REPLY on ppp999/28,trans-id=0xd529db, 3 opts: 2 1 25 Apr 25 23:39:46 router.local DibblerClient[243730]: PD: Adding 2001:1ae9:xxxx:xx00:: prefix to PD (iaid=1) to addrDB. Apr 25 23:39:46 router.local DibblerClient[243730]: PD: Adding prefix 2001:1ae9:xxxx:xx00::/56 to all interfaces (prefix will be split to /64 prefixes if necessary). Apr 25 23:39:46 router.local DibblerClient[243730]: PD: Using 1 suitable interface(s):br0.2 Apr 25 23:39:46 router.local DibblerClient[243730]: PD: Adding prefix 2001:1ae9:xxxx:xx00::/56 on the br0.2/13 interface. Apr 25 23:39:46 router.local DibblerClient[243730]: RENEW(IA_PD) will be sent (T1) after 1800, REBIND (T2) after 2880 seconds. Apr 25 23:39:46 router.local DibblerClient[243730]: About to execute /etc/dibbler/client-notify.sh script, 15 variables. Apr 25 23:39:46 router.local DibblerClient[243730]: Script execution complete, return code=0 Apr 25 23:39:46 router.local DibblerClient[243730]: Sleeping for 1799 second(s). == Issue #2: Post-install script fails == This is yet another issue spotted as part of the installation. Maybe it would deserve a separate bug report - who knows. Anyway, here it is: The postinst script fails, causing the whole installation to fail: Preparing to unpack .../dibbler-client_1.0.1-1build2_amd64.deb ... Unpacking dibbler-client (1.0.1-1build2) over (1.0.1-1build2) ... Setting up dibbler-client (1.0.1-1build2) ... Error: The new file /etc/dibbler/client.conf-dpkg-new does not exist! dpkg: error processing package dibbler-client (--install): installed dibbler-client package post-installation script subprocess returned error exit status 1 Processing triggers for man-db (2.10.2-1) ... Errors were encountered while processing: dibbler-client This can be fixed by creating the conf-new version at all times: --- dibbler-client.postinst 2022-04-25 23:52:28.891596403 +0200 +++ /home/work/dibbler/dibbler-1.0.1/debian/dibbler-client.postinst 2022-04-25 23:39:40.203950174 +0200 @@ -30,6 +30,8 @@ rm ${CONFFILE_NEW} fi +touch ${CONFFILE_NEW} + db_version 2.0 #db_capb backup db_settitle dibbler-client/title == Issue #3: Dibbler rebuild fails == To rebuild the package on Ubuntu 22.04 with the latest gcc, I had to supply the "-fcommon" flag explicitly: apt-get source dibbler-client apt-get build-deps dibbler-client cd dibbler-1.0.1/ DEB_CPPFLAGS_SET="-fcommon" DEB_CFLAGS_SET="-fcommon" debuild -us -uc -i -b Without that backwards compatibility flag, build fails on this error: /usr/bin/ld: libLowLevel_a-interface.o (symbol from plugin): in function `daemon_log': (.text+0x0): multiple definition of `interface_do_message'; libLowLevel_a-lowlevel-linux-link-state.o (symbol from plugin):(.text+0x0): first defined here /usr/bin/ld: libLowLevel_a-interface.o (symbol from plugin): in function `daemon_log': (.text+0x0): multiple definition of `interface_auto_up'; libLowLevel_a-lowlevel-linux-link-state.o (symbol from plugin):(.text+0x0): first defined here ./Port-linux/daemon.h:30:5: warning: 'die' violates the C++ One Definition Rule [-Wodr] 30 | int die(const char * pidfile); | ^ Port-linux/daemon.cpp:191:6: note: return value type mismatch 191 | void die(const char * pidfile) { | ^ Port-linux/daemon.cpp:191:6: note: type 'void' should match type 'int' Port-linux/daemon.cpp:191:6: note: 'die' was previously declared here collect2: error: ld returned 1 exit status make[2]: *** [Makefile:629: dibbler-client] Error 1 make[2]: Leaving directory '/home/work/dibbler/dibbler-1.0.1' make[1]: *** [Makefile:816: all-recursive] Error 1 make[1]: Leaving directory '/home/work/dibbler/dibbler-1.0.1' dh_auto_build: error: make -j1 returned exit code 2 make: *** [debian/rules:16: build] Error 25 dpkg-buildpackage: error: debian/rules build subprocess returned exit status 2 debuild: fatal error at line 1182: dpkg-buildpackage -us -uc -ui -i -b failed == System info == # lsb_release -rd Description: Ubuntu 22.04 LTS Release: 22.04 # Shows my rebuild/patched version # apt-cache policy dibbler-client dibbler-client: Installed: 1.0.1-1build2 Candidate: 1.0.1-1.1 Version table: 1.0.1-1.1 500 500 http://cz.archive.ubuntu.com/ubuntu jammy/universe amd64 Packages *** 1.0.1-1build2 100 100 /var/lib/dpkg/status 翻译
10-24
<think>我们面对的是一个关于Dibbler软件在Ubuntu 22.04上的三个问题的报告。用户提供了详细的日志和修复方法。以下是关键问题的总结: ### 问题1: Dibbler绑定到错误的PPP接口链路本地地址 - **现象**:在PPPoE接口(ppp999)上,Dibbler客户端尝试绑定到远程的链路本地地址(fe80::427c:7dff:fe93:d231)而不是本地的链路本地地址(fe80::8157:b42e:45ec:96ae),导致绑定失败(错误:Cannot assign requested address)。 - **根本原因**:Dibbler的底层Linux代码(`lowlevel-linux.c`)错误地使用了`IFLA_ADDRESS`(二层MAC地址)而不是`IFA_LOCAL`(本地IPv6地址)来获取链路本地地址。 - **修复**:修改代码,将`IFLA_ADDRESS`替换为`IFA_LOCAL`(已提供补丁)。 ### 问题2: 安装后脚本失败 - **现象**:安装dibbler-client时,postinst脚本因找不到`/etc/dibbler/client.conf-dpkg-new`件而失败。 - **修复**:在postinst脚本中创建该件(已提供补丁:添加`touch ${CONFFILE_NEW}`)。 ### 问题3: 编译失败 - **现象**:在Ubuntu 22.04上使用新GCC编译时,因缺少`-fcommon`标志导致多重定义错误。 - **修复**:在编译时显式添加`-fcommon`标志(已提供命令示例)。 ### 系统信息 - Ubuntu 22.04 LTS - 用户已手动重建并修复了包(版本标记为1.0.1-1build2)。 ### 建议行动 1. 应用问题1的代码补丁。 2. 修改postinst脚本(问题2)。 3. 在构建配置中加入`-fcommon`(问题3)。 4. 鉴于软件已停止维护,建议用户考虑替代方案(如ISC DHCP或dhcpcd),或自行维护补丁版本。 §§相关问题§§ 1. 在IPv6 PPPoE环境中,为什么链路本地地址的本地端和远程端需要区分绑定? 2. 除了修改`lowlevel-linux.c`,还有哪些方法可以正确获取PPP接口的本地链路本地地址? 3. 如何为已停止维护的开源软件(如Dibbler)创建可持续维护的Debian补丁包? 4. 当GCC默认行为变更(如-fno-common)导致旧代码编译失败时,有哪些系统化的调试方法? 5. Debian的postinst脚本中`db_version`和`db_settitle`的作用是什么? </think>### 问题分析与解释 用户报告的 Dibbler 软件在 Ubuntu 22.04 上的三个核心问题如下: --- #### **问题 1:Dibbler 绑定到错误的 PPP 接口链路本地地址** - **现象**: - Dibbler 客户端尝试绑定到 PPPoE 接口 `ppp999` 的**远程链路本地地址**(`fe80::427c:7dff:fe93:d231`)而非本地地址(`fe80::8157:b42e:45ec:96ae`)。 - 错误日志: ``` Creating socket (addr=fe80::427c:7dff:fe93:d231) on ppp999/28 interface. Unable to bind socket: Cannot assign requested address ``` - **根本原因**: - Dibbler 底层代码错误地使用了 `IFLA_ADDRESS`(二层链路地址)而非 `IFA_LOCAL`(本地三层地址)获取 IPv6 地址。 - 在点对点接口(如 PPPoE)中,本地地址需通过 `IFA_LOCAL` 获取,而非通用接口的 `IFLA_ADDRESS`。 - **修复方案**: - 修改源码 `lowlevel-linux.c`: ```c // 原代码(错误) memcpy(addr, (char*)RTA_DATA(rta_tb[IFLA_ADDRESS]), 16); // 修正后 memcpy(addr, (char*)RTA_DATA(rta_tb[IFA_LOCAL]), 16); ``` - 修正后客户端能正常绑定本地地址并获取 IPv6 前缀。 --- #### **问题 2:安装后脚本失败** - **现象**: - Debian 包安装时 postinst 脚本因缺少临时件 `/etc/dibbler/client.conf-dpkg-new` 报错: ``` Error: The new file /etc/dibbler/client.conf-dpkg-new does not exist! ``` - **修复方案**: - 修改 `dibbler-client.postinst` 脚本,确保件存在: ```bash # 在脚本中添加 touch ${CONFFILE_NEW} ``` --- #### **问题 3:编译失败** - **现象**: - 在 GCC 10+ 环境中编译时,因缺少 `-fcommon` 标志导致符号重复定义错误: ``` multiple definition of `interface_do_message' ``` - **根本原因**: - GCC 10 默认启用 `-fno-common`,而旧代码未明确定义全局变量。 - **修复方案**: - 编译时显式添加 `-fcommon` 标志: ```bash DEB_CPPFLAGS_SET="-fcommon" DEB_CFLAGS_SET="-fcommon" debuild -us -uc ``` --- ### 深层问题总结 1. **代码过时**:Dibbler 源码未适配 Linux 网络栈更新(如 PPP 接口的地址获取逻辑)。 2. **维护缺失**:软件已停止维护,但仍在 Ubuntu 仓库中提供,导致兼容性问题。 3. **构建系统缺陷**:未预判编译器行为变更(GCC 10 的 `-fno-common` 默认启用)。 --- ### 建议解决方案 1. **短期**:应用用户提供的补丁自行编译(需修复问题 1-3)。 2. **长期**: - 迁移到维护活跃的 DHCPv6 客户端(如 `dhcpcd` 或 `ISC Kea`)。 - 向 Ubuntu 提交修复后的软件包(包含三项补丁)。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值