跨平台C和C++ API中字符串的当前最佳实践是什么?

10

看起来我可能需要开始进行一些跨平台项目,其中部分需要使用C或C++(尚未决定因此问题涉及两者)。我将主要处理基于文本的内容和字符串。

那个C/C++将具有可从更高级别的平台相关代码调用的API。

我的问题是:在声明公共接口时,使用哪种类型最为可取?是否有任何推荐的标准技术?有需要避免的事情吗?

我对编写C或C++代码的经验很少,甚至那也只是在Windows上,所以根本没有跨平台的经验。因此,我真正寻找的是能够让我走上正确道路并避免做些愚蠢的事情,这些事情注定会带来很多痛苦。


编辑1:为了更好地说明预期的使用情况。

  • Objective C通过NSString和其他友元函数在iPhone/iPad/Mac上使用API。API可以静态链接,因此不需要担心.so .dll问题。

  • Java通过Android和其他Java平台上的JNI使用

  • 使用C++/CLI时,.NET通过托管的C#代码或本地静态链接的p/invoke使用。

  • 在这种情况下,有一些关于如何/何时使用lua的想法。不知道这是否对任何事情有影响。


我建议您以最舒适的风格编写所需的功能。我个人认为,在接口中看到std :: string每天都能拯救我的生命,但我不能确定对您是否适用。解决了这个问题后,再解决为每个绑定编写适配器的单独问题。它们是不同的问题。 - Tom Kerr
4个回答

15

规则

  • 使用UTF格式存储字符串,而不是“代码页”或类似方法(UTF-16可能更容易编辑:我完全忘记了字节顺序问题;UTF-8可能是更好的选择)。

  • 使用以null结尾的字符串而不是计数字符串,因为这些字符串最容易从大多数语言中访问。但要小心缓冲区溢出。
    6年后的更新: 我之所以推荐此API是出于互操作性的原因(因为许多人已经使用了null结尾,并且有多种表示计数字符串的方式),而不是从最佳设计的角度进行考虑。今天如果可以的话,我可能会说前者不太重要,建议使用计数字符串而不是以null结尾的字符串。

  • 甚至不要尝试使用像std::string这样的类将字符串传递给/从用户。即使在升级编译器/库之后,你自己的程序也可能会出现问题(因为它们的实现细节只是实现细节),更不用说非C++程序会遇到麻烦了。
    6年后的更新: 这仅仅是出于与其他语言的语言和ABI兼容性的考虑,而不是C++程序开发的通用建议。如果你正在进行C++开发,跨平台或者其他方面的话,请使用STL!也就是说,只有在需要从其他语言调用你的代码时才需要遵循这个建议。

  • 除非用户无法忍受,否则避免为用户分配字符串。相反,使用缓冲区并填充数据。这样,您不必强制用户使用特定的函数来释放数据。(这通常也会提高性能,因为它允许用户在堆栈上分配小的缓冲区。但如果您这样做,请提供您自己的函数来释放数据。您不能假设您的mallocnew可以用其freedelete释放——它们通常不能。)

  • 注意:

    只是为了澄清,“让用户分配缓冲区”和“使用空终止字符串”并不冲突。您仍然需要从用户那里获取缓冲区长度,但在终止字符串时包含NULL 。我的意思不是您应该创建类似于scanf(“%s”)的函数,该函数显然是无法使用的危险函数-您仍然需要从用户那里获取缓冲区长度。即在这方面几乎要做Windows所做的事情。


    7
    根据他所做的事情,UTF-8 可能是最容易的。 - James Kanze
    3
    @James:我本来想提到这一点,但我担心读者可能会不正确地开始使用UTF-8与___A版本的Windows API函数(而不是正确地转换为有点麻烦的UTF-16),所以我说了UTF-16。但是,如果您不太关心这种潜在的意外,那么UTF-8确实可能是最容易的选择。 - user541686
    1
    @STATUS_ACCESS_DENIED:这里的重点是互操作性,而不是安全性。只有一种方法可以进行空终止,但有多种方法可以进行长度计数(例如BSTR,一个带有指针和长度的数组结构等)。 - user541686
    4
    在某些平台上,wchar_t 是16位(例如Windows),而在其他地方是32位(例如Linux、Mac OS X),因此在内存布局上要小心。对于文件名,Windows 使用 UTF-16 格式,Linux 和 Mac 使用 UTF-8 格式,但两者之间容易进行转换。UTF-8 容易处理,因为通常不会出现字节顺序问题。在 UTF-16 中,您可能需要描述字节顺序。而且 wchar_t 理论上可以是 UTF-16 或 UTF-32,MSB 或 LSB。 这就是为什么在协议中使用 UTF-8 更加简单的原因。 - Tamas Demjen
    @0xC0000022L:六年后我看到了这个答案,意识到我同意你的观点。当时我不确定自己为什么会有这样的想法(我记得当时认为我有充分的理由,但也许我没有),但很抱歉我从未回复过你。今天我可能会同意你的看法。 - user541686
    显示剩余6条评论

    4
    C/C++将拥有一个可从更高级别的平台相关代码调用的API。
    如果您的意思是希望这个库可以作为DLL从其他语言(例如.NET语言)中调用,那么我强烈建议所有公共API都是只有POD类型参数和返回值的extern "C"函数。也就是说,优先选择/*const*/ char*而不是std::string。请记住,与纯C不同,C++没有标准ABI。

    4
    如果您想使用10吨级的锤子来处理C/C++中的字符串,那么IBM的ICU项目就是为您而设的。http://site.icu-project.org/ ICU具备所有用于处理字符串的工具,并提供非常好的Unicode支持。这是一个令人印象深刻且维护良好的开源产品,适用于商业项目的有利许可证。
    如果您想将代码发布为.dll/.so以供他人调用,则可能希望尽量减少外部依赖。在这种情况下,您可能希望坚持使用标准库或更轻量级的项目。

    这是非常棒的链接,谢谢。而且这可能是我会使用的东西。 - Philip P.
    @komrade-p 前进,走向最终的胜利!! - DWoldrich

    1
    一种非常常见的将字符串返回给调用者的方法是接受一个字符串缓冲区指针和缓冲区大小的字符数。一个有用的惯例是将复制到缓冲区中的字符数作为返回值;如果您将缓冲区大小视为特殊情况,并返回所需的字符数(包括空终止符),则这尤其有价值。
    int GetString(char * buffer, int buffersize);
    

    在C++中,使用std::string很方便,但这也带来了一个问题:您不能指望std::string的实现在程序的不同编译部分之间是兼容的,例如在主程序和库之间。通过在头文件中提供内联函数,您可以确保std::string在调用者相同的上下文中创建,并绕过此问题。
    inline std::string GetString()
    {
        std::string result(GetString(NULL, 0), 0);
        GetString(&result[0], result.size());
        result.erase(result.size() - 1);
        return result;
    }
    

    @Dennis,它会删除终止的空值,这不应该是字符串的一部分。这是由Microsoft的实现提供的,但我在http://www.cplusplus.com/reference/string/string/上找不到它,所以它必须是一个增强功能。我会修复代码。 - Mark Ransom
    不,那是官方接口的一部分,我只是不理解你为什么这样做。不过那样做确实有道理。 - Dennis Zickefoose
    显然,这只是0x中官方接口的一部分。 - Dennis Zickefoose

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接