为啥使用 GLib 函数?
Posted
技术标签:
【中文标题】为啥使用 GLib 函数?【英文标题】:Why use GLib functions?为什么使用 GLib 函数? 【发布时间】:2011-01-15 10:59:53 【问题描述】:使用 C 和 GTK+ 进行编程时,为什么使用 g_strdup_printf
、g_free
、g_strcmp0
等“更好”以及其他 GLib 函数?
【问题讨论】:
我将“glibc”标签更改为“glib”。请注意,这两者是完全不同的东西 - glibc 是 GNU C 库,用于(除非使用其他实现)在 Linux 上编译可执行文件,以便它们可以使用系统调用和标准函数。 glib 是很久以前来自 GTK 项目的更高级别的实用函数集。 避免使用 glib。你最终会在你的项目中编译 10,000 行糟糕的代码,以获得像链表这样微不足道的东西——当然,这是一个非常昂贵和抽象的东西——这需要你包含 glib 库的大部分其余部分,因为这个混乱是可怕地交织在一起。 【参考方案1】:一般来说,GLib 的目的是一个实用和可移植的库。这些本身就是考虑使用它的理由。
您提到的特定函数都在其 C 标准库变体之上提供了一些额外的东西:
g_strdup_printf
与 sprintf
类似,但实际上为您分配了缓冲区,让您不必猜测缓冲区应该有多大。 (返回值应该是g_free
'd。)
g_free
类似于 free
,但会检查 NULL 指针。
g_strcmp0
类似于 strcmp
,但将 NULL 指针视为空字符串,因此将其排在前面。
【讨论】:
标准的free
被定义为如果传递了NULL
指针则为空操作,因此g_free
没有提供任何奖励。
将g_malloc()
与g_free()
匹配而不是来自libc 的free()
仍然是一个好主意。我观察到在 Windows 上,多个 DLL 可能链接到 malloc()
的不同版本(具有单独的内部堆结构),并且混合和匹配会导致崩溃。不确定这是现实世界的问题,还是在其他平台上可能是这样,但通过使用 GLIB 的包装器,您可以避免这种情况。
我对任何考虑使用 Gnome 的人的建议是使用 C++ 作为 STL。您可以使用 STL 编写“无类 C++” - 直接 C。效果很好。 Gnome 项目中范围蔓延的数量简直是荒谬的。他们真的认为自己比编写 C 的人更聪明,并且每时每刻都试图“修复”该语言。它不需要修复。
@user2548100 说“他们认为自己比编写 C 的人更聪明”真的很贴切,而实际上他们只是选择以自己喜欢和适合的特定方式使用开放语言他们的目的。您不必同意它们的用法或偏好,但不要编造关于它们贬低语言或其架构师的荒谬故事。我也认为 C++ 与此完全正交,但是,嘿。
@Chris Lutz:一些在野外的实现会在free(NULL)
上失败(我自己在一个嵌入式项目中看到了这个)。 C 标准是一回事,现实世界的必需品是另一回事。这也是在另一个平台上看到它的人:***.com/a/1938758/518853。这就是 GLib 的用途:现实世界的可移植性。【参考方案2】:
为了在多个操作系统中保持一致的行为。这是一个便携的东西。
在 Linux 以外的一些其他 unix 环境中,或者如果您的程序是在 Windows 上编译的,则其中一些功能可能不存在或在目标操作系统上表现不同。
使用 glib 版本确保行为一致。
【讨论】:
C89 本身就是世界上最标准的语言。您更愿意调试哪个平台差异,C89 或 C89 包装在一些现在晦涩难懂的 Gnome 包装器中?是的,我也是。 不,不是。 Visual Studio 不支持它 (***.com/questions/146381/…)。此外,C99 和 C89 非常有限——对于大多数重要的项目,您至少需要一些来自 POSIX/Windows API 的函数(想想 dlopen、线程、套接字、原子、内存映射文件......),所以你需要某种可移植性层。【参考方案3】:GLib 提供了当今任何编程语言所期望的可移植性和基本功能,例如集合类型(链表、数组、哈希表等)。以下是 GLib 可以为您带来的一些好处。
便携性
GLib 的重点不是移植到 C 标准,而是移植到标准的实现。 GLib 会处理乍一看似乎无用的已知怪癖,直到您需要将代码移植到具有这些讨厌的错误的平台。
让我们以g_free
为例,因为许多人批评它。 There are platforms where free(NULL)
will fail,即使 C99 说它应该可以工作。该检查至少从 1998 年就存在(我在 git 历史记录中对其进行了跟踪)。有人可能会说不再需要它,但即使在 2017 年,我还在一家检查 NULL
的公司工作,然后再调用 free
,因为否则它会在他们的嵌入式平台上崩溃。当您想要进行一些重要的内存调试时,它还可以作为代码插入的包装器。
可读性
它通过提供一些包装函数来帮助提高代码的可读性,这些函数不仅可以提高可移植性,还可以帮助您避免许多语言陷阱。你们中有多少人测试malloc
看它是否返回NULL
?如果返回NULL
,你们中有多少人有办法恢复,因为您基本上内存不足?
g_malloc
将在应用程序无法分配您想要的内容时中止它,而在许多应用程序中,这正是您想要的行为。对于可能失败的非常大的分配,您有g_try_malloc
。这与 malloc 相同,但仍为您提供了作为可用于检测的包装器的好处。
会写:
char *buffer = g_malloc(30);
/* Do something with it ... */
g_free (buffer);
...解放思想,让开发人员专注于她想要完成的任务。它还可以避免您的程序在很久以后崩溃,因为它正在尝试使用NULL
指针进行写入,并且您必须跟踪分配。
标准 C 库充满了陷阱,不必对您编写的每一行代码进行微管理是一种解脱。只需阅读手册页的 BUGS 部分了解某些功能,您就会明白。更少的样板代码来检查错误使代码更易于阅读,从而提高了可维护性并减少了错误。
特点
另一点是 GLib 提供的所有集合类型,您不必重新实现。仅仅因为重新实现一个链表很容易并不意味着你应该这样做。我曾在另一家公司工作,该公司的代码带有多个链表实现,因为一些开发人员只会有 Not Invented Here 综合症并且会重新开发自己的。像 GLib 这样的通用的、经过彻底测试的、广泛使用的库有助于避免这种废话。除非您有非常具体的性能限制,否则您不应该重新开发这些东西。
【讨论】:
任何赤裸裸的 free() 充其量只是个菜鸟。没有人声称自己是专业的 C 程序员会这样做。 NIH 是一个问题,但解决方案不是使用别人的黑块,而是使用函数和模块化代码。除此之外,GLib 版本的很多东西,尤其是链表,非常抽象,但也非常昂贵并且容易出现内存泄漏。如果存储 blob,让用户为每个链接的存储分配内存,以及为每个链接点击 malloc() 的 API 可能是可以的,但如果知道链接大小,zilch 的服务会很复杂【参考方案4】:它们的行为在 GTK+ 支持的任何平台上都是明确定义的,而不是有时可能会部分工作的本机函数。
【讨论】:
根据我的经验,C 比 Gnome 更可靠。当然,在很多情况下,如果你深入研究 Gnome 代码,Gnome 函数只是标准 C 函数的#define。顺便说一句,了解函数在 linux/Unix 平台上如何工作的最佳方法是使用手册页。 “C”不是库,“libc”支持的内容因平台而异。 如果让您感觉更好的是研究您使用的每个平台的功能并从其他项目中提取代码而不是了解跨平台工具包,那么我不会阻止您。 或者,更像是,所说的本机函数可能根本不存在,或者至少将以完全不同的方式实现,否则会导致宏的无尽噩梦。然后是非常复古的 C89 的所有 polyfill(IMO 令人钦佩,但相关性越来越低;在这一点上,它可能更多的是启用而不是帮助)。无论如何,较新的 G* 版本将开始需要 C99 - 终于!【参考方案5】:我不得不说,这是出于好意,但执行得并不好。当您尝试多次释放指针时,您的程序不会崩溃和烧毁,这有点好。或对 NULL 字符串进行排序。但这是喜忧参半。它还可以防止您在代码中发现这些讨厌的错误。不仅有一天你会很难移植你的程序,因为你依赖于非标准函数,而且你会遇到真的困难,因为你的代码有问题而且你从来没有找到出去。
【讨论】:
标准规定free(NULL)
(或空指针上的任何空闲)是完全安全的并且是无操作的。那里没有任何好处。
这里必须同意汉斯。我正在接管一些遗留代码,我很清楚,4 个连续的 C 程序员团队已经被吓坏了,为了将其带入标准 C 89 必须进行的所有更改,他们投入并留下了巨大的混乱.例如,我很高兴 Gnome 的链表实现我将不得不从头开始重写,或者,如果我可以摆动它,将这段代码转换为 C++,以便我可以使用 STL。顺便说一句,带有 STL 的普通老式香草 C 可以正常工作,除了匿名结构 - C++ 脑放屁。
“不仅有一天你会很难移植你的程序,因为你依赖于非标准函数”是错误的。 GLib 的部分目的是简化平台之间的移植——它提供了围绕许多看似标准的函数的封装 API,纯粹是因为它们的行为在平台之间确实存在差异,而 GLib 抽象了这些差异。
@RocketRoy 至少你清楚地表明了你的偏见......以及你对细节的缺乏关注,这让他们失去了信誉。 GNOME 是一个桌面。 GLib 是它使用的库之一。而且您刚刚将成千上万曾经为这两者做出贡献的人视为“傲慢的军队”,更糟糕的是,与“另一个才华横溢的程序员”相互排斥,例如可能是您自己。至少我们知道在这里进行实际讨论是没有意义的(但对于一个谩骂比赛来说,这是该去的地方)。
@RocketRoy:有正当理由使用或不使用 GLib,这取决于您在编写代码时要达到的目标(可移植性、易于开发、访问大量项目)使用 GLib 等)。用滥用和夸张的方式表达你的论点对任何人(我、你或任何试图阅读此线程并从中获得建议的人)都没有帮助。我希望你能从过去对 GLib 的任何创伤中继续前进,并从你的生活中消除一些仇恨。我在这里完成了。 :-)【参考方案6】:
10 年前,使用 Gnome 库可能是有意义的,但现在它已成为遗留问题。 C89 可以说是世界上最标准的语言,具有非常稳定的特性和语法,因此调试别人的 C89 代码是可行的。
相比之下,Gnome 的 glib 在 C 标准之外更改了它的函数特性,因此您不仅可以调试由 C 制成的晦涩的包装器代码,而且您的代码可能会因为 Gnome 更改它的包装器函数而停止工作。
图表 A:g_snprintf()
标准 sprintf() 函数的一种更安全的形式。输出是 保证不超过 n 个字符(包括终止的 nul 字符),因此很容易确保缓冲区溢出不会 发生。
另见 g_strdup_printf()。
在 1.2.3 之前的 GLib 版本中,如果 输出被截断,截断的字符串可能不会 nul 终止。在 1.3.12 之前的版本中,此函数返回 输出字符串的长度。
g_snprintf()的返回值符合snprintf()函数 符合 ISO C99 标准。请注意,这不同于 传统的 snprintf(),它返回输出字符串的长度。
格式字符串可能包含位置参数,如 单一 Unix 规范。
我不太高兴能编写(又一个)链表来替换 Gnome 的,还有另一个版本的 snprintf() 和一堆蹩脚的包装代码,它们默默地占用 malloc() 的内存,从而破坏了C 编码的一个绝对最大值:“始终在同一范围内 malloc() 和 free()” 替换 g_strdup_printf()。
g_strdup_printf()
类似于标准 C sprintf() 函数,但更安全,因为它计算所需的最大空间并分配内存来保存结果。返回的字符串应该是 不再需要时使用 g_free() 释放。
加上在代码中进行大量字符串更改以执行“有用”的事情的快感,例如将 gchar 更改为 char、gint 到 int、gboolean 到 bool 等,等等,直到我的 Subversion 比较令人作呕现在是电话簿。更糟糕的是,您最终不得不更改越来越多的代码,因为这些东西散落在 .h 文件中,所以它会不断扩大,就像一具被划船的尸体,变成一团糟。
如果您正在确定一份合同工作的范围并在任何地方查看 glib.h,请运行! 就说不!
PS: 下载源代码,删除所有 Gnome 特定类型,然后重新编译它以使您自己的“g_”-less 函数有点工作,并且可以节省大量时间。
图表 B:g_strdup_printf()
来自 Gnome 的更可怕的 Gnome crappola。 Gnome 有很多“可爱”的函数,比如 g_strdup_vprintf(),它们“神奇地”知道你需要多少存储空间来保存返回的字符串,我有机会看看背后的“魔法”。这为我赢得了有史以来最可怕的 C 滥用奖。
如果你通过所有的包装函数继续追踪 g_strdup_vprintf(),你就会来到 gmessages.c 中的这个 gem....
/**
* g_printf_string_upper_bound:
* @format: the format string. See the printf() documentation
* @args: the parameters to be inserted into the format string
*
* Calculates the maximum space needed to store the output
* of the sprintf() function.
*
* Returns: the maximum space needed to store the formatted string
*/
gsize
g_printf_string_upper_bound (const gchar *format,
va_list args)
gchar c;
return _g_vsnprintf (&c, 1, format, args) + 1;
不仅任何 printf() 函数在绝对为零时都比 snot 慢,您会注意到它们分配了整个字节,是的,整个 gchar c 用于存储,这保证了溢出,但是谁在乎呢?他们获得了 malloc() 所需的字符串的长度——因为他们将转身并再次执行整个 printf()——这一次,“神奇地”只有足够的存储空间。
当然,它们会在大小上加 1,因此您将有空间容纳一个 nul 终止符,当然,这肯定会付出可怕的代价,但他们已经把它隐藏在代码中的深处了打赌你会放弃并盲目使用它,而不会注意到这是多么大的脑放屁。啧啧,谢谢各位。我只是喜欢我的代码爬行。
不要让 _g_vsnprintf() 函数让你失望,因为在 gprintfint.h 中你会发现 little gem 只是普通老式 vsnprintf() 的另一个名称;
/* GLIB - Library of useful routines for C programming
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GLib Team and others 2002. See the AUTHORS
* file for a list of people on the GLib Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GLib at ftp://ftp.gtk.org/pub/gtk/.
*/
#ifndef __G_PRINTFINT_H__
#define __G_PRINTFINT_H__
#ifdef HAVE_GOOD_PRINTF
#define _g_printf printf
#define _g_fprintf fprintf
#define _g_sprintf sprintf
#define _g_snprintf snprintf
#define _g_vprintf vprintf
#define _g_vfprintf vfprintf
#define _g_vsprintf vsprintf
#define _g_vsnprintf vsnprintf
#else
#include "gnulib/printf.h"
#define _g_printf _g_gnulib_printf
#define _g_fprintf _g_gnulib_fprintf
#define _g_sprintf _g_gnulib_sprintf
#define _g_snprintf _g_gnulib_snprintf
#define _g_vprintf _g_gnulib_vprintf
#define _g_vfprintf _g_gnulib_vfprintf
#define _g_vsprintf _g_gnulib_vsprintf
#define _g_vsnprintf _g_gnulib_vsnprintf
#endif
#endif /* __G_PRINTF_H__ */
强烈建议您在使用 Gnome 之前再次观看绿野仙踪,这样您就知道不要看幕后。欢迎来到我的噩梦!
任何认为 Gnome 比 C 更稳定的人都严重缺乏批判性思维。您可以用性能和透明度换取一些在 STL 中做得更好的好东西。
【讨论】:
“总是 malloc() 和 free() 在同一个范围内”让我发笑。说真的,在现实世界的应用程序中,您对事件做出反应,在某些事件上分配内存,销毁该内存以响应其他事件。 GLib 记录了谁负责释放内存,谁拥有它,这就是你想要的。 顺便说一句,gboolean 早于 C99 bool 类型。 GLib 开始于 1996 年左右。 gboolean 被 gint 的原因在这里:git.gnome.org/browse/glib/tree/ChangeLog.pre-1-2#n2532 您显示了_g_vsnprintf
定义,但完全消除了#ifdef HAVE_GOOD_PRINTF
的漏洞,这表明这样做是为了便于携带。我可以揭穿所有其他陈述,但看到你懒得只运行git blame
看看为什么在抱怨之前出现了一些代码,我不会做你的功课。
这只是一篇写得很糟糕的文章,使用的反对意见大部分适用于任何你认为可接受的基线(C89 或 POSIX)之上的任何库。 “它现在是遗留问题” 没有任何意义,因为它仍在积极开发并不断积累现代功能。差异已记录在案,“遗留”功能是可选的并且一直被删除(除了无害的功能,例如,现在很傻,g
typedefs)等等。当简单地使用 GLib 阅读一些现代代码会为您做到这一点时,揭穿这是不值得的任务 - 但您当然不希望那样......
@RocketRoy 关于 GLib 维护者在 1996 年启动它,在设计它之前应该阅读 2012 年的一篇文章......我想他们忘记了他们的水晶球在未来阅读。【参考方案7】:
这是一个更新。看起来开发者意识到了他们的错误:
g_mem_is_system_malloc 自 2.46 版以来已被弃用,不应在新编写的代码中使用。
GLib 总是使用系统 malloc,所以这个函数总是返回 TRUE。
检查 g_malloc() 使用的分配器是否是系统的 malloc 实现。如果它返回 TRUE,则使用 malloc() 分配的内存可以与使用 g_malloc() 分配的内存互换使用。此函数对于避免非基于 GLib 的 API 返回的已分配内存的额外副本很有用。
https://developer.gnome.org/glib/stable/glib-Memory-Allocation.html#g-mem-is-system-malloc
【讨论】:
删除 GLib 的 malloc vtable 不仅仅是一个“错误”更复杂。它被删除是因为对 ctor 支持的需求被认为比 vtable 启用的内存分析功能更重要;与此同时,libc 的支持分析支持显着成熟。见bugzilla.gnome.org/show_bug.cgi?id=751592。 @PhilipWithnall 我的意思是首先添加它是一个错误。删除它不是。倒是一件好事。 当时(虽然我不在,所以这是基于一点考古学和我所听到的),我相信添加它是有道理的;当时对使用其他方法和工具分析内存分配的支持很差。 GLib 已经存在(并且 API 稳定)很长时间了。以上是关于为啥使用 GLib 函数?的主要内容,如果未能解决你的问题,请参考以下文章
linux 进程间通信 dbus-glib实例详解四(上) C库 dbus-glib 使用(附代码)(编写接口描述文件.xml,dbus-binding-tool工具生成绑定文件)(列集散集函数)