【问题标题】:printf with a size_t using the FFI使用 FFI 的带有 size_t 的 printf
【发布时间】:2018-04-20 13:33:59
【问题描述】:

为了在 C 中用printf 打印一个size_t 整数,转换格式器是%zu

但是,当我将printf%zu 一起使用时,通过FFI 调用Haskell 中的C 函数会打印zu 而不是整数。如何解决?

小例子

文件 zu.c

#include <stdio.h>

void printzu(){
    size_t x = 666;
    printf("x=%zu", x);
}

模块 Lib.hs

{-# LANGUAGE ForeignFunctionInterface #-}
module Lib
  where
import Foreign

foreign import ccall unsafe "printzu" printzu' :: IO ()

测试

Prelude> import Lib
Prelude Lib> printzu'
x=zu

【问题讨论】:

  • 这是一个过时的 C 标准库的味道,它已经在 haskell 进程中链接了。这是否偶然发生在Windows上?可能是旧的 MSVCRT.DLL ;)
  • 您可以通过使用%lu 并明确地将x 转换为unsigned long 来解决它——在size_t 大于unsigned long 和你的程序实际上处理的尺寸这么大......
  • @FelixPalmen 是的,Windows。未在 Linux 上测试。
  • 好的,我会试试你的解决方法。目前我只是使用%u 而不是%zu,没有强制转换,这可行,但我在我的 C 编辑器中收到了一些乏味的警告。
  • 您可以使用一些流程浏览器工具检查在这两种情况下哪些模块是动态链接的。我敢打赌,从 haskell 运行时,您会从 Windows 系统文件夹中获得 MSVCRT.DLL。这个不支持%zu,至少不通过公开可见的界面。

标签: c haskell ffi


【解决方案1】:

由于printf() 是C 标准库的一部分,它通常在一些运行时库中实现。当这是动态链接时,如果根据哪个 Process 调用代码,链接了不同版本的库,则可以使用相同的代码产生这种效果。如果%zu不起作用,那是老版本还不支持C99。

在 Windows 上,很可能是系统的 MSVCRT.DLL,它不再供公众使用,但与旧的 MS Visual C 6 版本保持兼容。例如 MinGW 默认链接到该库,因此您不需要发布自己的 C 运行时。这当然有将库函数限制为 C89/C90 的缺点。

打印size_t 通常相当安全的做法是将其转换为unsigned long 并打印:

size_t x = 666;
printf("x=%lu", (unsigned long)x);

这只会给出错误的结果,如果

  • 该平台实际上有一个比unsigned long 更大的size_t(这是正确的,例如,对于带有 LLP64 数据模型的 64 位系统,不幸的是,win64)和
  • 您在运行时的大小确实不适合unsigned long。这必须至少是大于 4G (232) 的值,因为这是unsigned long 的保证最小范围。

请注意,演员在这里非常重要。因为printf() 是一个可变参数函数,原型看起来就像printf(const char *fmt, ...),所以没有可用的编译器类型信息——因此无法进行自动转换。


如果问题是特定于 MSVCRT.DLL,并且您通常希望坚持使用 C99 或更高版本,I suggested a method using inttypes.h in an earlier answer。这将永远不会在 Windows 上打印错误的值(并且在其他平台上仍然需要符合 C99 的标准库)。

【讨论】:

  • 请注意,Windows(64 位)恰好是unsigned longsize_t 的等级不同或更高的一种真实情况,因此这可能不是最好的建议,除非您只是想忽略(默默地打印错误的值)非常大尺寸的可能性。
  • @R.. 我想我准确地说明了何时可以打印错误的值。 unsigned long 需要至少 32 个值位,因此始终可以保存高达 4G 的任何值。对于错误的结果,我可以在第二个条件中明确说明这一点。恕我直言,这对于 大多数 打印尺寸的实际用例来说已经足够了。当然不是所有人。
  • 确实您确实提到了它,而我的评论并没有很好地反映。我想指出的主要一点是,Win64 是要点 1 失败的独特情况。
  • @R.. 我添加了更多细节,因此每个读者都可以更好地自行判断这种方法对于他的用例是安全的还是有风险的。
  • @chqrlie 对于 windows,有一个更好的解决方案:Microsoft 有自己的格式说明符,它们存在于每个版本中,最多 64 位。 mingw 附带的标头将它们映射到inttypes.h 中的PRI* 宏。因此,您可以转换为 uint64_t 并使用这些宏 -> 符合标准且不截断。
【解决方案2】:

"%zu" 未实现时,另一种方法是转换为某种宽类型并打印,但截断风险不大。

size_t sz = foo();
printf("%lu\n", (unsigned long) sz);  // risk of truncation.

代码可以尝试其他整数宽类型,如uintmax_tunsigned long long,但如果"%zu" 未实现,那么"%ju""%llu" 可能也不会实现。

部分打印可以避免截断。

printf("%lX%08lX\n", 
    (unsigned long) (sz/0x10000u/0x10000u), (unsigned long) (sz & 0xFFFFFFFFu));

// remote truncation risk remains.
printf("%lu%09lu\n", 
    (unsigned long) (sz/1000000000u), (unsigned long) (sz%1000000000u));

可以使用更复杂的代码来避免前导数字。

【讨论】:

  • 如果%llu没有实现,应该完全避免使用该系统。此外,使用 2 个部分的 printf 格式有些不正确。仅当sizeof(long) &gt; 4 时才应使用该代码,并且十进制格式将至少有一个前导0,如果值小于100000000,则有多个前导。
  • @chqrlie "如果 %llu 没有实现,系统应该完全避免。" %llu最早出现在C99同样,如果您拥有 %llu,那么您非常很可能也拥有%zu
  • C99 出现在 19 年前......仍然没有实现这一点的系统应该被淘汰。
  • @chqrlie 太粗了。微软长期宣布msvcrt.dll“私有”的系统范围安装,并通过他们的编译器产品提供更新/更好的运行时库,因此,为他们“解决了问题”。有些人不喜欢用 C 语言编写的每个应用程序重新编译一个完整的标准库——尽管已经计划(可选地)用 mingw 替换 msvcrt.dll,但能够编译它可以被认为是一件好事精益 Windows 的动态链接 C 程序。使用这个古老的图书馆真的没有很多限制。
  • @chqrlie 不清楚“代码仅应在sizeof(long) &gt; 4 时使用”。你指的是printf("%lu\n", (unsigned long) sz);printf("%lX%08lX\n", ... 还是两者兼而有之?如果不是第一种情况下的截断或第二种情况下的前导 0 问题,请详细说明“有些不正确”。这两个问题都在答案中注明。也许文本应该更多地强调这些? &gt; 4 部分特别不清楚,因为我预计任何大小/范围限制都是由于 unsigned longsize_t 而不是固定值 4(如 4 字节类型)。
【解决方案3】:

我想提供另一种方法来处理不符合 C99/C11 标准但提供 64 位或更宽类型的系统。

导入并包含 stdint.h/inttypes.h,旨在将旧系统与新的 C99 标准连接起来。

示例:C99 stdint.h header and MS Visual Studio

然后转换为通过它们可用的宽类型

#if SIZE_MAX > ULONG_MAX
// Include from the standard location or wherever the imported included files are saved.
#include <stdint.h>
#include <inttypes.h>

void printzu(){
    size_t x = 666;
    printf("x=%" PRIuMAX "\n", (uint_max_t) x);
}

#else
void printzu(){
    size_t x = 666;
    printf("x=%lu\n", (unsigned long) x);
}
#endif

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-05
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    • 2016-12-18
    • 2011-03-11
    • 1970-01-01
    相关资源
    最近更新 更多