【问题标题】:The use of size_t in an array iterator在数组迭代器中使用 size_t
【发布时间】:2014-05-07 21:18:15
【问题描述】:

我最近了解到,引入 size_t 是为了帮助面向未来的代码防止本机位数增加和可用内存增加。具体的使用定义好像是关于存储某个东西的大小,一般是数组。

我现在一定想知道这种未来的证明应该走多远。如果下一个迭代数组的任务使用unsigned int 作为索引数组,那么使用面向未来和适当大小的 size_t 定义数组长度当然是没有意义的:

void (double* vector, size_t vectorLength) {
    for (unsigned int i = 0; i < vectorLength; i++) {
        //...
    }
}

事实上,在这种情况下,我可能希望严格的语法应该将 unsigned int 上转换为关系运算符的 size_t。

这是否意味着迭代器变量i 应该只是一个size_t

这是否意味着任何程序中的任何整数都必须在功能上进行标识,以确定它是否会被用作数组索引?

这是否暗示任何使用以编程方式开发索引的逻辑的代码都应该创建一个 size_t 类型的新结果值,特别是如果该逻辑依赖于潜在的有符号整数值?即

double foo[100];
//...
int a = 4;
int b = -10;
int c = 50;

int index = a + b + c;
double d = foo[(size_t)index];

当然,因为我的代码逻辑创建了一个固定的界限,向上转换到 size_t 并没有提供额外的保护。

【问题讨论】:

  • size_t 应该是对象的最大可能大小。因此,您可以确定它可以用于索引char 的数组,无论该数组有多大。而且,如果您要计算malloc 的内存量,请确保不要溢出SIZE_MAX 等。
  • 在实践中,您不能真正使用索引不适合ptrdiff_t 的数组或向量,因此在这里使用无符号类型来制造问题是没有意义的。除非数组真的有可能变得太大而无法被int 索引,否则您应该使用int。 (在很多情况下,这种可能性根本不存在。有时根据定义,有时由于物理限制。)

标签: c++ c arrays size-t


【解决方案1】:

您应该牢记语言的自动转换规则。

这是否意味着迭代器变量 i 应该只是一个 size_t?

是的,因为如果size_t 大于unsigned int 并且您的数组实际上比unsigned int 可以索引的更大,那么您的变量(i)永远无法达到数组的大小.

这是否意味着任何程序中的任何整数都必须在功能上进行标识,以确定它是否会被用作数组索引?

你试图让它听起来很激烈,而事实并非如此。为什么选择变量为double 而不是float?为什么要将变量设为unsigned 而不是?为什么要创建一个变量short 而另一个变量是int?当然,你总是知道你的变量将被用来做什么,所以你决定它们应该得到什么类型。 size_t 的选择是众多选择之一,也是类似的决定。

换句话说,程序中的每个变量都应该在功能上进行识别并赋予正确的类型。

这是否暗示任何使用以编程方式开发索引的逻辑的代码都应该创建一个 size_t 类型的新结果值,特别是如果该逻辑依赖于潜在的有符号整数值?

一点也不。首先,如果变量永远不能有负值,那么它本来可以是unsigned intsize_t。其次,如果变量可以在计算过程中具有负值,那么你一定要确保它最终是非负的,因为你不应该用负数索引一个数组。

也就是说,如果您确定您的索引是非负数,那么将其转换为 size_t 并没有任何区别。 C11 在 6.5.2.1 说(强调我的):

后缀表达式后跟方括号中的表达式[] 是下标 指定数组对象的元素。 下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))。由于适用于二进制 + 运算符的转换规则,如果 E1 是一个数组对象(等效地,一个指向数组对象初始元素的指针)并且 E2 是一个整数,E1[E2] 指定 @987654341 E1 的@th 元素(从零开始计数)。

这意味着任何类型的indexsome_pointer + index 有意义,都可以用作索引。换句话说,如果您知道您的int 有足够的空间来包含您正在计算的索引,那么绝对没有必要将其转换为不同的类型。

【讨论】:

  • 感谢您的可靠回答。我使第二个问题听起来很激烈,因为它对需要更新到这样一个标准的现有代码有更大的影响。如果确实需要更新,那么它应该走多远,应该更新什么样的变量以及我需要升级现有代码的一般方法是什么。
  • 你可以用一个负数索引一个数组。如果你愿意,但你可能是对的,你不应该......
  • @Hiett,它可能索引一个指针,指向某个数组中的某个地方,带有负索引,当然。不过我不想深入细节。尽管如此,索引原始数组不应该有一个负索引,因为它是未定义的行为。请参阅 C11,索引 J2(未定义的行为),它具有以下内容:
  • - 将指针添加或减少到数组对象或仅超出数组对象和整数类型会产生不指向或仅超出同一数组对象的结果 (6.5.6) .
  • - 数组下标超出范围,即使对象显然可以使用给定的下标访问(如在左值表达式 a[1][7] 中给出声明 int a[4][5])(6.5.6)
【解决方案2】:

正如Neil Kirk 所讨论的,迭代器是size_t 的未来证明对应物。

您问题中的另一点是位置的计算,这通常包括 绝对 位置(例如您的示例中的 a)和可能一个或多个 相对 em> 个数量(例如 bc),可能签名

size_t 的签名对应物是ptrdiff_t,迭代器类型I 的类似物是typename I::difference_type

正如您在问题中所描述的,最好在代码中的任何地方使用适当的类型,这样就不需要转换。为了提高内存效率,如果你有例如一百万个位置的数组到其他数组中,你知道这些位置在 0-255 范围内,那么你可以使用unsigned char;但是在某些时候需要转换。

在这种情况下,最好命名这个类型,例如

using pos = unsigned char;

并明确所有转换。如果将来增加 0-255 的范围,那么代码将更易于维护。

【讨论】:

  • 整数类型的局部变量小于int 几乎是不合适的。唯一一次你会使用更小的东西(shortsigned char)是在一个大数组或向量中,或者一个被放入大数组或向量中的结构体中。
  • @JamesKanze 对,这就是我在示例中所说的:“一系列...职位”。此外,在执行操作(如加法)之前,无论如何都会提升这些类型。
【解决方案3】:

这是否意味着任何程序中的任何整数都必须在功能上进行标识,以确定它是否会被用作数组索引?

我会选择这一点,并明确地说。此外,在大多数情况下,用作数组索引的变量仅用作那个(或与之相关的东西)。

这条规则不仅适用于这里,还适用于其他情况:现在有许多用例存在特殊类型:ptrdiff_toff_t(甚至可能会根据我们使用的配置而改变!) 、pid_t 和许多其他人。

【讨论】:

    【解决方案4】:

    如果下一个迭代数组的任务使用无符号整数作为索引数组,那么使用面向未来和适当大小的 size_t 定义数组长度当然是没有意义的

    是的。所以不要这样做。

    事实上,在这种情况下,我可能希望严格的语法应该将 unsigned int 上转换为关系运算符的 size_t。

    它只会在特定的&lt; 操作中被提升。您的 int 变量的上限不会改变,因此 ++ 操作将始终使用 int,而不是 size_t。

    这是否意味着迭代器变量 i 应该只是一个 size_t?

    这是否意味着任何程序中的任何整数都必须在功能上进行标识,以确定它是否会被用作数组索引?

    是的,它比 int 好...但是有一个更聪明的方法来编写程序:使用常识。每当您声明一个数组时,您实际上可以停止并提前考虑该数组可能需要存储多少项。如果它永远不会包含超过 100 个项目,那么您绝对没有理由使用 int 或使用 size_t 对其进行索引。

    在 100 个项目的情况下,只需使用 uint_fast8_t。然后程序针对大小和速度进行了优化,并且 100% 可移植。

    每当声明一个变量时,一个优秀的程序员都会激活他们的大脑并考虑以下几点:

    • 我将存储在此变量中的值的范围是多少?
    • 我真的需要在其中存储负数吗?
    • 对于数组,在最坏的情况下我需要多少个值? (如果不知道,是否必须使用动态内存?)
    • 如果我决定移植此程序,此变量是否存在兼容性问题?

    与一个糟糕的程序员相反,他们不会激活他们的大脑,只是在整个地方输入int

    【讨论】:

    • 不知道uint_fast8_t! C 提供了很多很少人知道的东西!
    【解决方案5】:

    是的,如果你使用 int 来索引一个数组,你就违背了在其他地方使用 size_t 的意义。这就是您可以在 STL 中使用迭代器的原因。它们是未来的证明。对于 C 数组,您可以使用 size_t、指针或算法以及 lambda 或基于范围的 for 循环 (C++11)。如果您需要将大小或索引存储在变量中,它们将需要是 size_t 或其他适当的类型,它们与之交互的任何其他内容也一样,除非您知道大小会很小。 (例如,如果你存储两个元素之间的距离总是在一个很小的范围内,你可以使用 int)。

    double *my_array;
    for (double *it = my_array, *end_it = my_array + my_array_size, it != end_it; ++it)
    {
        // use it
    }
    
    std::for_each(std::begin(my_array), std::end(my_array), [](double& x)
    {
        // use x
    });
    
    for (auto& x : my_array)
    {
        // use x
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-06-25
      • 2021-12-30
      • 1970-01-01
      • 2013-08-04
      • 2015-11-29
      • 2023-01-28
      • 2019-02-17
      • 1970-01-01
      相关资源
      最近更新 更多