8

我想用 C++ 解析 UTF-8。解析新字符时,我事先不知道它是 ASCII 字节还是多字节字符的前导,也不知道我的输入字符串是否足够长以包含剩余的字符。

为简单起见,我想将接下来的四个字节命名为a、和b,并且因为我在 C++ 中,所以我想使用引用来完成它。cd

只要我在知道访问是安全的之前不访问它们,在函数的开头定义这些引用是否有效?例子:

void parse_utf8_character(const string s) {
    for (size_t i = 0; i < s.size();) {
        const char &a = s[i];
        const char &b = s[i + 1];
        const char &c = s[i + 2];
        const char &d = s[i + 3];

        if (is_ascii(a)) {
            i += 1;
            do_something_only_with(a);
        } else if (is_twobyte_leader(a)) {
            i += 2;
            if (is_safe_to_access_b()) {
                do_something_only_with(a, b);
            }
        }
        ...
     }
}

上面的例子显示了我想要在语义上做的事情。它没有说明我为什么要这样做,但显然真正的代码会涉及更多,所以只有当我知道访问是安全的并且我需要它们时才定义 b、c、d 会太冗长。

4

3 回答 3

5

对此有三种看法:

  • 形式上
    很好,谁知道呢。我可以用相当长的时间为你找出答案,但是,你也可以。或任何读者。而且这并不是非常实用。
    编辑:好的,查一下,因为你似乎不高兴我提到正式而不为你查一下。形式上你不走运:
    N3280 (C++11) §5.7/5 “如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则评估不得产生溢出;否则,行为是未定义的。”
    这可能会产生不希望的行为的两种情况:(1) 计算超出段末尾的地址,以及 (2) 在启用调试检查的情况下计算超出编译器知道大小的数组的地址。

  • 从技术上讲
    ,只要您避免任何左值到右值的转换,您可能就可以了,因为如果引用被实现为指针,那么它与指针一样安全,并且如果编译器选择将它们实现为别名,那也是好的。

  • 在经济
    上不必要地依赖微妙之处会浪费你的时间,然后也会浪费其他人处理代码的时间。所以,不是一个好主意。相反,在保证它们所指的内容存在时声明名称。

于 2012-12-09T22:54:04.823 回答
4

在讨论对不可访问内存的引用的合法性之前,您的代码中还有另一个问题。您的调用s[i+x]可能会string::operator[]使用更大的参数调用 then s.size()。C++11 标准说string::operator[][string.access], §21.4.5):

要求:pos <= size()。

返回: *(begin()+pos) 如果 pos < size(),否则引用类型为 T 且值为 charT() 的对象;参考值不得修改。

这意味着调用s[x]x > s.size()未定义的行为,因此实现可以很好地终止您的程序,例如通过断言的方式。

由于string现在保证是连续的,因此您可以使用 &s[i]+x 来解决该问题以获取地址。在实践中,这可能会奏效。

然而,不幸的是,严格来说这样做仍然是非法的。这样做的原因是,该标准只允许指针算术,只要指针位于同一数组内,或者在数组末尾之后。(C++11) 标准的相关部分在 [expr.add], §5.7.5 中:

如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为是未定义的。

因此,生成指向无效内存位置的引用或指针可能适用于大多数实现,但它在技术上是未定义的行为,即使您从不取消引用指针/使用引用。依赖 UB 几乎从来都不是一个好主意,因为即使它适用于所有目标系统,也不能保证它在未来继续有效。

于 2012-12-09T23:09:13.570 回答
2

原则上,对可能非法的内存地址进行引用的想法本身是完全合法的。引用只是引擎盖下的指针,并且指针算术在解除引用发生之前是合法的。

编辑:此声明是实际声明,而不是已发布标准涵盖的声明。已发布标准的许多角落是形式上未定义的行为,但在实践中不会产生任何类型的意外行为。

以在数组结束后计算指向第二个项目的指针的可能性为例(正如@DanielTrebbien 建议的那样)。该标准说溢出可能导致未定义的行为。实际上,只有当数组的上端刚好短于指针可寻址的空间时,才会发生溢出。不太可能发生的情况。即使它确实发生了,在大多数架构上也不会发生任何不好的事情。违反的是关于指针差异的某些保证,此处不适用。

@JoSo如果您使用的是字符数组,则可以通过在代码中用常量指针替换常量引用来避免一些关于引用语义的不确定性。这样你就可以确定没有编译器会对这些值起别名。

于 2012-12-09T22:51:00.653 回答