繁体   English   中英

在针对size_t测试循环变量时,使用权威的“正确”方法来避免签名无符号警告

[英]Authoritative “correct” way to avoid signed-unsigned warnings when testing a loop variable against size_t

下面的代码生成编译器警告:

private void test()
{
    byte buffer[100];
    for (int i = 0; i < sizeof(buffer); ++i)
    {
        buffer[i] = 0;
    }
}

警告:有符号和无符号整数表达式之间的比较[-Wsign-compare]

这是因为sizeof()返回一个unsigned的size_t。

我已经看到了一些关于如何处理这个问题的建议,但没有一个有优势支持,没有一个有任何令人信服的逻辑,也没有提到支持一种方法显然“更好”。 最常见的建议似乎是:

  1. 忽略警告
  2. 关掉警告
  3. 使用size_t类型的循环变量
  4. 使用带有技巧的size_t类型的循环变量来避免减少过零
  5. 将size_of(缓冲区)转换为int
  6. 一些非常复杂的建议,我没有耐心跟随,因为它们涉及不可读的代码,通常涉及向量和/或迭代器
  7. 我经常使用的AVR / ARM嵌入式环境中无法加载的库。
  8. free函数返回一个有效的int或long,表示T的字节数
  9. 不要使用循环(得到喜欢的建议)

是否有“正确”的方法来解决这个问题?

- 开始编辑 -

我给出的示例当然是微不足道的,仅用于演示索引情况下可能出现的类型不匹配警告。

#3 不一定是明显正确的答案,因为size_t在递减循环中带有特殊风险,例如for (size_t i = myArray.size; i > 0; --i) (数组有朝一日可能大小为零)。

#4是一个建议,通过包括适当和必要的检查来处理减少size_t索引,以避免减少过去的零。 由于这会使代码难以阅读,因此有一些可爱的快捷方式不是特别易读,因此我将它们称为“技巧”。

#7是建议使用不可推广的库,因为它们可能在每个设置中都不可用或不合适。

#8是建议保持检查可读,但隐藏它们的非成员方法,有时称为“自由功能”。

#9是使用算法而不是循环的建议。 作为size_t索引问题的解决方案,这次提供了很多次,并且有很多赞成。 我包括它,即使我不能在我的大多数环境中使用stl库,并且必须自己编写代码。

- 结束编辑 -

我希望有关于基于证据的指导或参考,以便处理类似这样的事情。 是否存在解决问题的“标准文本”或样式指南? 一种主流技术公司内部采纳/认可的既定方法? 一种可以在新语言版本中出现的可模拟解决方案? 如有必要,我会对一位得到广泛认可的专家提供的不受支持的公众推荐感到满意。

提供的选项似乎都没有吸引力。 警告淹没了我想看到的其他东西。 我不想错过可能重要的地方签名/未签名的比较。 使用比较> = 0递减size_t类型的循环变量会导致无符号整数回绕的无限循环,即使我们使用for (size_t i = sizeof(buffer); i-->0 ;) ,增加/减少/比较size_t变量还有其他问题。 当size_t意外为零时(例如strlen(myEmptyString) ),对size_t - 1测试将产生一个大的正'oops'数。 将无符号size_t转换为整数是容器大小问题(不保证值),当然size_t可能大于int。

鉴于我的数组已知大小远低于Int_Max,在我看来,将size_t转换为有符号整数是最好的,但它让我有点畏缩。 特别是如果它必须是static_cast<int> 如果它通过一些尺寸测试隐藏在函数调用中,则更容易拍摄,但仍然......

或者也许有办法关闭警告,但只是为了循环比较?

我发现以下三种方法中的任何一种都同样好。

  1. 使用int类型的变量来存储大小并将循环变量与它进行比较。

     byte buffer[100]; int size = sizeof(buffer); for (int i = 0; i < size; ++i) { buffer[i] = 0; } 
  2. 使用size_t作为循环变量的类型。

     byte buffer[100]; for (size_t i = 0; i < sizeof(buffer); ++i) { buffer[i] = 0; } 
  3. 使用指针。

     byte buffer[100]; byte* end = buffer + sizeof(buffer) for (byte* p = buffer; p < end; ++p) { *p = 0; } 

如果您能够使用C ++ 11编译器,则还可以使用范围for循环。

byte buffer[100];
for (byte& b : buffer)
{
    b = 0;
}

最合适的解决方案完全取决于上下文。 在您的问题中的代码片段的上下文中,最合适的操作可能是类型协议 - 子弹列表中的第三个选项。 这在这种情况下是合适的,因为在整个代码中使用i只是为了索引数组 - 在这种情况下,使用int是不合适的 - 或者至少是不必要的。

另一方面,如果i是一个算术对象涉及一些本身已经签名的算术表达式,那么int可能是合适的,并且一个强制转换将是有序的。

我建议作为一个指导原则,一个涉及最少数量的必要类型转换(隐式显式)的解决方案是合适的,或者以另一种方式来看,最大可能的类型协议。 没有一个“权威”规则,因为涉及的变量的目的和用法在语义上而不是语法上依赖。 在这种情况下,如在其他答案中已经指出的那样,支持迭代的较新语言特征可以完全避免该特定问题。

要讨论你说的具体建议:

  • 忽略警告

永远不是一个好主意 - 有些是真正的语义错误或维护问题,而且当你忽略几百个警告时,你会如何发现一个警告是什么和问题?

  • 关掉警告

更糟糕的想法; 编译器正在帮助您提高代码质量和可靠性。 你为什么要禁用它?

  • 使用size_t类型的循环变量

在这个精确的例子中,这正是你应该做的原因; 确切类型协议应始终是目标。

  • 使用带有技巧的size_t类型的循环变量来避免减少过零

这个建议与给出的微不足道的例子无关。 此外,我认为通过“技巧”,顾问实际上意味着检查或只是正确的代码 没有必要使用“技巧”,这个术语完全含糊不清 - 谁知道顾问意味着什么? 当不需要任何具有这种属性的解决方案时,它会提出一些非传统的和有点“脏”的东西。

  • 将size_of(缓冲区)转换为int

如果使用i保证在代码中的其他地方使用int来获得正确的语义,那么这可能是必要的。 问题中的例子没有,所以在这种情况下这不是一个合适的解决方案。 从本质上讲,如果让i一个size_t这里别处导致协议类型警告说,不能用自己的表达式中的所有操作数万能型协议来解决,那么投可能是合适的。 目标应该是实现最小类型演员的零警告。

  • 一些极其复杂的建议,我没有耐心跟随,通常涉及向量和/或迭代器

如果您不准备详细说明或甚至考虑这些建议,您最好省略您的问题中的“建议”。 在任何情况下,在任何情况下使用STL容器并不总是适合于大部分嵌入式目标,过多的代码大小增加和非确定性堆管理是在许多平台和应用程序上避免的原因。

  • 我无法在嵌入式环境中加载的库。

并非所有嵌入式环境都具有相同的约 限制在您的嵌入式环境中,而不是所有嵌入式环境。 然而,解决或避免类型协议问题的“加载库”似乎是一个破解坚果的大锤。

  • free函数返回一个有效的int或long,表示T的字节数

目前尚不清楚这意味着什么。 什么是“ 自由功能 ”? 这只是一个非会员职能吗? 这样的函数在内部必然会有一个类型的情况,那么除了隐藏类型转换之外,你取得了什么?

  • 不要使用循环(必须喜欢这个建议)。

我怀疑你需要在你的清单中包含这些建议。 问题在任何情况下都不限于循环; 这不是因为你正在使用一个循环,你有警告,这是因为你使用了<与不匹配的类型。

我最喜欢的解决方案是使用C ++ 11或更新版本并完全跳过整个手动大小边界:

// assuming byte is defined by something like using byte = std::uint8_t;

void test()
{
    byte buffer[100];
    for (auto&& b: buffer)
    {
        b = 0;
    }
}

或者,如果我不能使用基于范围的for循环(但仍然可以使用C ++ 11或更新版本),我最喜欢的语法变为:

void test()
{
    byte buffer[100];
    for (auto i = decltype(sizeof(buffer)){0}; i < sizeof(buffer); ++i)
    {
        buffer[i] = 0;
    }
}

或者向后迭代:

void test()
{
    byte buffer[100];
    // relies on the defined modwrap semantics behavior for unsigned integers
    for (auto i = sizeof(buffer) - 1; i < sizeof(buffer); --i)
    {
        buffer[i] = 0;
    }
}

正确的通用方法是使用size_t类型的循环迭代器。 仅仅因为它是用于描述数组大小的最正确的类型。

没有太多需要“避免减少过零的技巧”,因为对象的大小永远不会是负面的。

如果您发现自己需要负数来描述变量大小,可能是因为您有一些特殊情况,您在向后迭代数组。 如果是这样,处理它的“技巧”是这样的:

for(size_t i=0; i<sizeof(array); i++)
{
  size_t index = sizeof(array)-1 - i;
  array[index] = something;
}

但是, size_t通常是在嵌入式系统中使用的一种不方便的类型,因为它最终可能比MCU用一条指令处理的类型更大,导致代码效率低下。 如果您事先知道数组的最大大小,那么使用固定宽度整数(如uint16_t可能会更好。

在嵌入式系统中使用plain int几乎肯定是不正确的做法。 您的变量必须具有确定性大小和签名 - 嵌入式系统中的大多数变量都是无符号的。 无论何时需要使用按位运算符,有符号变量也会导致严重问题。

如果您能够使用C ++ 11,则可以使用decltype来获取sizeof返回的实际类型,例如:

void test()
{
    byte buffer[100];
    // On macOS decltype(sizeof(buffer)) returns unsigned long, this passes
    // the compiler without warnings.
    for (decltype(sizeof(buffer)) i = 0; i < sizeof(buffer); ++i)
    {
        buffer[i] = 0;
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM