最近以提问的形式发了几条关于编程思维的微博,有的获得了粉丝的响应,有些却无人问津。今天突然想到可以将我对这些问题的思考整理成一篇博文,以便与大家分享。另外,我计划以“编程思维训练”为名写一些系列文章,希望通过这类文章与读者分享我在编程活动中的一些思考。
问:有一对内存管理函数MemAlloc和MemFree,使用形式分别是:分配时为“void *p = MemAlloc (6)",释放时为“MemFree (&p)”。你能看出什么问题吗?有什么建议?
答:一谈到内存分配与释放函数,我们很自然地会想到来自C库的malloc和free函数。然而,MemFree与free在入参类型的定义上却存在很大的差别,前者需要传入的是指针的指针,而后者所需的却是指针。由于MemFree函数的入参形式与free存在区别,这就容易造成使用者容易因为忽视这种区别而在初次使用时犯错。
尽管MemFree的函数参数定义成指针的指针存在一定的好处 — 可以在MemFree函数中对所释放内存的指针变量置NULL,但由于它违背了malloc/free的使用常识,因而不可取。我的建议是,定义API名称时,应尽可能符合“常识”。
问:malloc与free是C库中用于从堆中分配和释放内存的函数。这两个函数在命名上有什么问题?如果给你重新命名的机会,你会如何做?
答:很显然,malloc函数是“memory allocate”的简写,那相应的内存释放函数应是“memory free”,如果采用一致的命名,free函数的名称应是mfree。因此,C库中的malloc/free函数存在命名一致性问题。
问:在Linux操作系统中,所有的外设都位于/dev目录之下以文件的形式进行管理的,能否将socket也纳入到/dev目录之下呢?如果是你,你会如何做?为什么要这样做?
答:将外设放入/dev目录,使得在Linux中操作外设(或设备)与操作文件是相似的。这种设计一方面减少了API的数量,另一方面,使得程序员很容易掌握设备的操控编程。然而,Linux中的socket却采用了完全不同的设计实现。
在Linux中如要打开一个socket,则必须调用socket函数,且在函数中指定是流类型(stream),还是数据报类型(datagram),以及所需打开的端口号。然而,从TCP/IP协议的角度来看,我们知道“流”对应的是TCP协议,而“数据报”对应的是UDP协议。这种映射关系尽管简单(但并不易懂),但不可避免的是,它仍然引入了“映射”这一行为(复杂化了)。
如果将socket也按目录的方式进行管理,那么打开一个socket也可以用open函数,且目录结构可以考虑采用协议名进行组织。比如,采用“open (“/net/ipv4/tcp/3344”, O_CREAT)”的形式打开一个端口号为3344的TCP socket,而采用“open (“/net/ipv4/udp/3344”, O_CREAT)”的形式打开一个UDP的socket。很显然,这种方式并不需要引入“流”与“数据报”的概念,且简少了API的数量。总体说来,基于目录结构的socket设计将使得Linux在概念上更加一致。
实际上,目前的Linux中socket也是一种类型的fd(file descriptor),尽管造成目前的现状有很多历史原因,但它仍可以向前迈进一步。