在 AIX 上替代 asprintf 或解决方法
Posted
技术标签:
【中文标题】在 AIX 上替代 asprintf 或解决方法【英文标题】:Substitute or workaround for asprintf on AIX 【发布时间】:2011-06-21 10:00:35 【问题描述】:我正在尝试在 AIX 上构建 python-kerberos。 kerberospw.c 使用对 asprintf 的调用,但从 Google 告诉我的内容来看,AIX 上不存在 asprintf。
我看到了http://www.koders.com/c/fidAA9B130D588302673A28B568430A83131B7734C0.aspx?s=windows.h,看起来我可以创建一个替代 asprintf,但我不知道它会去哪里或如何#include 它在 kerberospw.c 中。
有没有办法可以使用 koders.com 示例或其他代码来“伪造”asprintf?我可以只包含 kerberospw.c 中所示的 asprintf 函数吗?我不是 C 程序员,但
asprintf (char **resultp, const char *format, ...)
对我来说,最后的点看起来不像是一个有效的签名。 kerberospw.c 的相关行如下
asprintf(&message, "%.*s: %.*s", (int) result_code_string.length, (char *) result_code_string.data, (int) result_string.length, (char *) result_string.data);
我意识到我可以联系 python-kerberos 的作者,但是 a) 我认为如果我这样做的话,有一个潜在的补丁会很有帮助,并且 b) 我可能遇到其他使用 asprintf 的软件,并且有一个解决方法会很好。
【问题讨论】:
请注意,asprintf()
的手册页指出,*resultp
中的返回值在出现错误时是不确定的。其他手册页,例如问题中引用的手册页,表明它在错误时显式设置为 NULL。如果使用asprintf()
,则不要假设函数失败时指针已初始化。如果实现asprintf()
,请确保指针在出错时设置为 null 以提供确定性行为。
【参考方案1】:
asprintf
是printf
系列函数的变体,它分配一个缓冲区来保存格式化字符串的内存并返回它。它是一个具有可变数量参数的函数(因此声明中的 ...
是有效的 C 代码)。你可以找到描述here。
如果vsnprintf
正常运行,则可以相对轻松地重新实现它(即,如果缓冲区太小而无法容纳格式化的字符串,则返回错误)。
这是一个这样的实现:
#include <stdarg.h>
int asprintf(char **ret, const char *format, ...)
va_list ap;
*ret = NULL; /* Ensure value can be passed to free() */
va_start(ap, format);
int count = vsnprintf(NULL, 0, format, ap);
va_end(ap);
if (count >= 0)
char* buffer = malloc(count + 1);
if (buffer == NULL)
return -1;
va_start(ap, format);
count = vsnprintf(buffer, count + 1, format, ap);
va_end(ap);
if (count < 0)
free(buffer);
return count;
*ret = buffer;
return count;
【讨论】:
您必须在第一次调用vsnprintf()
之后调用va_end(ap);
,并在第二次调用之前再调用va_start(ap, format);
。当然,您还需要#include <stdlib.h>
和#include <stdio.h>
以获得vsnprintf()
的声明。
啊,谢谢。我总是忘记去做。我会更新我的帖子。
另外,buffer
超出了*ret = buffer;
行的范围。
谢谢西尔万。我进行了测试,这可以使代码编译并使用 python 代码提交的测试用例成功测试。
对于任何希望在 Windows 上实现 asprintf 的人,您不能使用这里使用的 vsnprintf
,因为 Windows 将 NULL
缓冲区输入视为错误。您可以将其替换为 _vscprintf
函数,它就像 vprintf
一样,只是它不打印任何内容 - 仅返回字符数,这正是我们想要的。【参考方案2】:
以Sylvain 的answer 为基础,这里有一个简单的实现,同时包含asprintf()
和vasprintf()
,因为在需要一个的地方,通常最终也需要另一个。而且,鉴于 C99 中的 va_copy()
宏,很容易根据 vasprintf()
实现 asprintf()
。实际上,在编写 varargs 函数时,将它们成对使用通常很有帮助,一个使用省略号表示法,一个使用 va_list
参数代替省略号,您可以根据后者轻松实现前者。
这导致代码:
int vasprintf(char **ret, const char *format, va_list args)
va_list copy;
va_copy(copy, args);
/* Make sure it is determinate, despite manuals indicating otherwise */
*ret = NULL;
int count = vsnprintf(NULL, 0, format, args);
if (count >= 0)
char *buffer = malloc(count + 1);
if (buffer == NULL)
count = -1;
else if ((count = vsnprintf(buffer, count + 1, format, copy)) < 0)
free(buffer);
else
*ret = buffer;
va_end(copy); // Each va_start() or va_copy() needs a va_end()
return count;
int asprintf(char **ret, const char *format, ...)
va_list args;
va_start(args, format);
int count = vasprintf(ret, format, args);
va_end(args);
return(count);
在没有提供这些函数的系统中使用这些函数的棘手部分是决定函数应该在哪里声明。理想情况下,它们会在<stdio.h>
中,但是您不需要编写它们。因此,您必须有一些其他标头,其中包括<stdio.h>
,但如果它们未在<stdio.h>
中声明,则声明这些函数。而且,理想情况下,代码应该半自动地检测到这一点。也许标题是"missing.h"
,并且包含(部分):
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdarg.h>
#ifndef HAVE_ASPRINTF
extern int asprintf(char **ret, const char *format, ...);
extern int vasprintf(char **ret, const char *format, va_list args);
#endif /* HAVE_ASPRINTF */
另外,请注意asprintf() 的手册页指出,如果出现错误,指针中的返回值是不确定的。其他man pages,包括问题中引用的那个,表明它在错误时被明确设置为NULL。 C 标准委员会文档 (n1337.pdf) 没有指定内存不足的错误行为。
如果使用 asprintf(),不要假设函数失败时指针已初始化。 如果实现 asprintf(),请确保指针在出错时设置为 null 以提供确定性行为。【讨论】:
谢谢乔纳森。您是对的,AIX 也没有新加载的 vasprint。我也对此进行了测试,它似乎编译得很好。我会赞成这个和 Sylvain 的答案,但我实际上没有足够的代表,因为我不是一个很好的 SO 参与者。 @bobwood:你应该接受——并且确实接受了——Sylvain 的回答;在我看来,这是完全正确的。他做了驴工作(在一点帮助下);我的回答是严格派生的。 AFAIK,你总是可以投票给答案——如果你觉得不止一个答案有帮助,你可以投票给多个答案。接受的答案是针对“最有帮助”的答案,并为回答者(以及作为提问者的您)提供额外奖励。 (我的答案的一个草稿版本以“不要对此投票”开头。我很贪心并删除了它。) 在此问题的答案中获得声誉并获得赞誉。 在分配错误时,您返回格式化字符串的长度,并且 ret 指向的指针未设置为 NULL。这意味着如果您引用未初始化的指针(这在 asprintf() 调用中很常见),分配失败的情况与成功的情况无法区分。 小问题:为什么同时使用*ret = 0;
和buffer != NULL
而不是0
或NULL
?【参考方案3】:
我来这里是为了寻找一个适用于 Windows 和 Linux 的快速实现,它在失败时将返回指针设置为 NULL。
Jonathan Leffler 的答案看起来更好,但后来我注意到 malloc 失败时它没有设置为 -1。
我进行了更多搜索,发现了这个discussion of implementing asprintf,这让我明白 Jonathan 和 Sylvain 都没有正确处理溢出。
我现在推荐this solution提供上述讨论,它似乎涵盖了所有重要平台,并且显然正确处理了每个故障场景。
【讨论】:
虽然我同意这些观点,但这个答案是对各种答案的评论。它不是answer。充其量是link-only answer。【参考方案4】:这里的实现在大多数情况下不会调用snprintf()
两次。如其他回复所示,我省略了包含和定义。
应该如此,将asprintf()
定义为对vasprintf()
的调用
int asprintf(char **dst, const char * pcFormat, ...)
va_list ap;
va_start(ap, pcFormat);
int len = vasprintf(dst, pcFormat, ap);
va_end(ap);
return len;
我们将缓冲区预分配到预定义的适当大小,并且仅在第二次溢出调用vsnprintf()
的情况下。基本原理是s*printf()
函数被认为非常繁重并且过度分配内存是可以接受的。
int vasprintf(char **dst, const char * pcFormat, va_list ap)
int len = 512; /* Worked quite well on our project */
int allocated = 0;
va_list ap_copy;
char *buff = NULL;
while(len >= allocated)
free(buff);
buff = malloc(len+1);
if(buff)
allocated = len+1;
va_copy(ap_copy, ap);
len = vsnprintf(buff, len+1, pcFormat, ap_copy);
va_end(ap_copy);
else /* malloc() failed */
return -1;
*dst = buff;
return len;
编辑:我用简单的malloc()
替换了realloc()
调用,因为它更便宜。在溢出的情况下,free()/malloc()
对的成本低于realloc()
,因为其内部隐藏了memcpy()
。当我们通过随后调用vsnprintf()
覆盖整个缓冲区时,该副本没有意义。
【讨论】:
以上是关于在 AIX 上替代 asprintf 或解决方法的主要内容,如果未能解决你的问题,请参考以下文章