【问题标题】:Help with \0 terminated strings in C#帮助在 C# 中使用 \0 终止的字符串
【发布时间】:2011-02-04 14:19:25
【问题描述】:

我正在使用一个低级原生 API,我在其中发送一个不安全的字节缓冲区指针来获取一个 c 字符串值。

所以它给了我

// using byte[255] c_str
string s = new string(Encoding.ASCII.GetChars(c_str));

// now s == "heresastring\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0(etc)";

所以很明显我做得不对,我如何摆脱多余的?

【问题讨论】:

  • 当我通过 RS-232 收到一个字符串时,我得到了类似的东西。最终我做错了:我发现每个接收到的字节都会调用处理程序,并且在处理程序中我使用 serialPortInstance.Read(...) 读取超过 1 个字节。
  • 我不确定,但可能会看看正则表达式,比如 string re1="((?:[a-z][a-z]+))";并获得第一场比赛
  • 以空字符结尾的字符串的“规则”是应该忽略以第一个空字符开头的所有内容。仅 Trim() 或 Replace() 的其他几个答案并未考虑在初始 null 之后可能存在一些非 null“垃圾”。 This answer 给出了一条线的解决方案。

标签: c# string cstring


【解决方案1】:

.NET 字符串不是以 null 结尾的(正如您可能已经猜到的那样)。因此,您可以像对待任何普通字符一样对待 '\0'。正常的字符串操作将为您解决问题。以下是一些(但不是全部)选项。

s = s.Trim('\0');

s = s.Replace("\0", "");

var strings =  s.Split(new char[] {'\0'}, StringSplitOptions.RemoveEmptyEntries);

如果您确实想丢弃第一个空字符之后的任何值,这可能对您更有效。但请注意,它仅适用于实际包含空字符的字符串。

s = s.Substring(0, Math.Max(0, s.IndexOf('\0')));

【讨论】:

  • 这些方法忽略了这样一个事实,即字符串中的第一个 null 之后很可能有非 null 字符。 This answer 提供了更强大的解决方案。
  • 嗯......这些方法中的任何一种如何在空值后丢失字符?修剪仅适用于字符串的末端。 Replace 不对字符串的任何部分执行任何操作,除了空字符。 Split 显式保留除空字符以外的所有内容,从而生成字符串数组。看起来每个选项都可以安全地处理任何字符串中的每个非空字符。
  • 您的解决方案适用于 OP 提供的特定字符串。但是从本机 (C++) API 返回的字符串可以在初始 null 之后包含垃圾。一般的解决方案必须忽略初始 null 之后的所有内容,而不仅仅是忽略 null(s)。在这个示例字符串(“这是一个字符串\0memoryjunkhere”)上尝试你的每个解决方案,看看我的意思。
  • @Richard:感谢您尝试澄清这一点,但我的回答并不是简单地复制并放入某人的应用程序中的代码。相反,它指出正常的字符串操作可以很容易地检测和操作空字符。如果开发人员想保留 \0 之后的内容,他可以。如果开发者不想要它,他可以忽略它。
  • OP 的问题中给出的上下文(“c-string value”,“get rid of the extra”)表明他们希望忽略第一个 null 之后的所有内容,即使他们可能不知道它然而 :-) 您昨天添加的方法更好,但正如您所提到的,仅当输入字符串包含至少一个 null 时才有效,需要调用者实现另一个“if”测试。我在最初的评论(@MrHIDEn)中引用的答案适用于所有这些场景。
【解决方案2】:

可能有一个选项可以在转换中去除 NUL。

除此之外,您还可以使用以下方法进行清理:

s = s.Trim('\0');

...或者,如果您认为某些 NUL 之后可能有非 NUL 字符,这可能更安全:

int pos = s.IndexOf('\0');  
if (pos >= 0)
    s = s.Substring(0, pos);

【讨论】:

    【解决方案3】:
    // s == "heresastring\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0(etc)"    
    s = s.Split(new[] { '\0' }, 2)[0];
    // s == "heresastring"
    

    【讨论】:

    • 太棒了!这是一个单语句答案,可以正确处理从本机 (C++) API 返回的字符串很可能在第一个 null 之后包含垃圾非 null 字符的场景。 (例如,“这是一个字符串\0memoryjunkhere”)其他一些答案不能正确处理这个重要场景(或需要 IF 测试)。
    • 这将创建一个临时数组,其中包含每个 null 的字符串。因此,如果我有一个包含 300 个 NUL (\0) 的缓冲区,那么将“hello”放在开头 - split 会给我一个大约 294 个空字符串的数组。我认为最好使用s = s.Substring(0, Math.Max(0, s.IndexOf('\0'))); 方法。
    • @TomLeys 您忘记了 Split() 方法的第二个“2”参数。在这种情况下,数组将包含 1 个或 2 个成员。
    • @Tom Leys 此代码将拆分为只有 2 或 1 个字符串的数组。检查 "abc\0\0\0\0"s.Split(new[] { '\0' }, 2), => String ["abc","\0\0\0"]
    【解决方案4】:

    System.Runtime.InteropServices.Marshall.PtrToString* 方法之一怎么样?

    Marshal.PtrToStringAnsi - 将所有字符从非托管 ANSI 字符串复制到第一个空字符到托管字符串,并将每个 ANSI 字符扩展为 Unicode。

    Marshal.PtrToStringUni - 分配一个托管字符串并将其全部或部分复制到非托管 Unicode 字符串的第一个空值。

    【讨论】:

    • 它看起来性能更好,你能举个例子吗谢谢。
    【解决方案5】:

    最安全的方法是使用:

    s = s.Replace("\0", "");
    

    【讨论】:

      【解决方案6】:

      我相信 \0 在 ascii 中是“null”——你确定你得到的字符串实际上是 ascii 编码的吗?

      【讨论】:

      • 我认为他的意思是他得到了一系列空字节,而不是他实际上得到了“\0”字符串序列。
      • 我想我会喜欢 .Trim("\0") 哈哈
      【解决方案7】:

      从 .NET Core 2.1 开始,可以使用以下内容来帮助防止对中间数组或字符串进行不必要的分配:

      var bytesAsSpan = bytes.AsSpan();
      var terminatorIndex = bytesAsSpan.IndexOf(byte.MinValue);
      var s = Encoding.ASCII.GetString(bytesAsSpan.Slice(0, terminatorIndex));
      

      这确实是需要 .NET Core 2.1 或更高版本的最后一行,因为那时引入了 Encoding.GetString(ReadOnlySpan<byte>) 重载。可以使用System.Memory 包执行基于Span 的操作,但Encoding.GetString 不会暴露接受ReadOnlySpan<byte> 的重载,因此最后一行必须分配一个数组:

      var s = Encoding.ASCII.GetString(bytesAsSpan.Slice(0, terminatorIndex).ToArray());
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-02-01
        • 2014-12-03
        • 1970-01-01
        • 2010-09-24
        • 2014-03-02
        • 1970-01-01
        • 1970-01-01
        • 2012-01-02
        相关资源
        最近更新 更多