【问题标题】:Calculating size of an array计算数组的大小
【发布时间】:2010-10-17 17:33:21
【问题描述】:

我正在使用以下宏来计算数组的大小:

#define G_N_ELEMENTS(arr) ((sizeof(arr))/(sizeof(arr[0])))  

但是,当我评估函数中数组的大小(计算的值不正确)与调用函数的位置(计算的值正确)相反时,我发现它计算的值存在差异。下面的代码+输出。任何想法、建议、提示等。欢迎。

DP

#include <stdio.h>

#define G_N_ELEMENTS(arr) ((sizeof(arr))/(sizeof(arr[0])))

void foo(int * arr) // Also tried foo(int arr[]), foo(int * & arr) 
                    // - neither of which worked
{
   printf("arr : %x\n", arr);
   printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));
}

int main()
{
   int arr[] = {1, 2, 3, 4};

   printf("arr : %x\n", arr);
   printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));

   foo(arr);
}

输出:

arr : bffffa40
sizeof arr: 4
arr : bffffa40
sizeof arr: 1

【问题讨论】:

  • 将 foo 参数称为同名会使您违反直觉。考虑给它另一个名字来模拟编译器正在查看的内容。

标签: c++ c


【解决方案1】:

现在我们在 C++11 中有 constexpr,类型安全(非宏)版本也可以在常量表达式中使用。

template<typename T, std::size_t size>
constexpr std::size_t array_size(T const (&)[size]) { return size; }

这将无法在无法正常工作的地方编译,这与您的宏解决方案不同(它不会意外地在指针上工作)。您可以在需要编译时常量的地方使用它:

int new_array[array_size(some_other_array)];

话虽如此,如果可能的话,你最好使用std::array。不要注意那些说使用std::vector的人,因为它更好。 std::vector 是一种不同的数据结构,具有不同的优势。 std::array 与 C 样式数组相比没有开销,但与 C 样式数组不同,它不会在最轻微的刺激下衰减为指针。另一方面,std::vector 要求所有访问都是间接访问(通过指针),并且使用它需要动态分配。如果您习惯使用 C 样式的数组,请记住一件事,那就是确保将 std::array 传递给这样的函数:

void f(std::array<int, 100> const & array);

如果不通过引用传递,则复制数据。这遵循大多数精心设计的类型的行为,但与传递给函数时的 C 样式数组不同(它更像是结构内部的 C 样式数组的行为)。

【讨论】:

    【解决方案2】:

    编辑:自编写此答案以来就引入了 C++11,它包含的功能与我在下面显示的完全一样:std::beginstd::end。常量版本 std::cbeginstd::cend 也将进入标准的未来版本(C++14?),并且可能已经在您的编译器中。如果您可以访问标准功能,甚至不要考虑使用下面的我的功能。


    我想在Benoît's answer 上做一些事情。

    不要只传递数组的起始地址作为指针,或者像其他人建议的那样传递一个指针加上大小,而是从标准库中获取提示,并将两个指针传递给数组的开头和结尾。这不仅使您的代码更像现代 C++,而且您可以在数组上使用任何标准库算法!

    template<typename T, int N>
    T * BEGIN(T (& array)[N])
    {
        return &array[0];
    }
    
    template<typename T, int N>
    T * END(T (& array)[N])
    {
        return &array[N];
    }
    
    template<typename T, int N>
    const T * BEGIN_CONST(const T (& array)[N])
    {
        return &array[0];
    }
    
    template<typename T, int N>
    const T * END_CONST(const T (& array)[N])
    {
        return &array[N];
    }
    
    void
    foo(int * begin, int * end)
    {
      printf("arr : %x\n", begin);
      printf ("sizeof arr: %d\n", end - begin);
    }
    
    int
    main()
    {
      int arr[] = {1, 2, 3, 4};
    
      printf("arr : %x\n", arr);
      printf ("sizeof arr: %d\n", END(arr) - BEGIN(arr));
    
      foo(BEGIN(arr), END(arr));
    }
    

    如果模板不起作用,这里是 BEGIN 和 END 的替代定义。

    #define BEGIN(array) array
    #define END(array) (array + sizeof(array)/sizeof(array[0]))
    

    更新: 上面带有模板的代码可以在 MS VC++2005 和 GCC 3.4.6 中正常工作。我需要一个新的编译器。

    我也在重新考虑这里使用的命名约定 - 伪装成宏的模板函数感觉不对。我相信我很快会在自己的代码中使用它,我想我会使用 ArrayBegin、ArrayEnd、ArrayConstBegin 和 ArrayConstEnd。

    【讨论】:

    • +1,好主意,但请将“T[N]& array”(不是有效的 C++ 类型,不会编译)更改为“T (&array)[N]” .
    • @j_random_hacker:你能解释一下为什么 T[N]& 不是有效的 C++ 类型吗?
    • @Benoit:这只是 C/C++ 语法的规则——它们需要数组边界标识符名称之后(此处隐含)。同样,要声明一个名为 foo 的 10 个整数数组,您需要编写“int foo[10];”不是“int[10] foo;” -- 尽管后者看起来很合理,但它不会解析。
    • 还有 boost::begin() 和 boost::end()。顺便说一句,我们还可以为 rbegin() 和 rend() 定义返回 std::reverse_iterator
    • 我会选择实用程序命名空间和简单名称:array::size、array::begin、array::end、array::cbegin(或类似的常量版本)...跨度>
    【解决方案3】:

    这是因为 int * 的大小是 int pointer 的大小(在我使用的现代平台上为 4 或 8 个字节,但它完全取决于平台)。 sizeof 是在编译时计算的,而不是在运行时计算的,所以即使 sizeof (arr[]) 也无济于事,因为您可能会在运行时调用 foo() 函数并使用许多不同大小的数组。

    一个int数组的大小就是一个int数组的大小。

    这是 C/C++ 中的棘手问题之一 - 数组和指针的使用并不总是相同的。在很多情况下,数组会衰减为指向该数组第一个元素的指针。

    至少有两种解决方案,同时兼容C和C++:

    • 将长度与数组一起传递(如果函数的意图是实际计算出数组大小,则很有用)。
    • 传递一个标记数据结束的标记值,例如,{1,2,3,4,-1}

    【讨论】:

      【解决方案4】:

      在 C++ 中,您可以像这样定义 G_N_ELEMENTS:

      template<typename T, size_t N> 
      size_t G_N_ELEMENTS( T (&array)[N] )
      {
        return N;
      }
      

      如果您希望在编译时使用数组大小​​,方法如下:

      // ArraySize
      template<typename T> 
      struct ArraySize;
      
      template<typename T, size_t N> 
      struct ArraySize<T[N]> 
      { 
        enum{ value = N };
      };
      

      感谢 j_random_hacker 纠正我的错误并提供更多信息。

      【讨论】:

      • 这是一个很好的替代定义,如果它传递了一个指针,它将无法编译。 +1
      • 虽然,我会将参数设为 const 引用。
      • 虽然您可能在编译时需要该值,但在这种情况下,更好的选择是:template struct G_N_ELEMENTS;模板 struct G_N_ELEMENTS { static size_t value = N; };
      • 另外:“T[N]& array”不是有效的 C++ 类型,不会编译。 "T (&array)[N]" 是。
      • +1。编辑:我修复了主 ArraySize 模板的定义(它必须有 1 个参数,而不是 2 个),并在枚举声明中添加了一个尾随分号。
      【解决方案5】:

      您应该只在数组上调用 sizeof。当您在指针类型上调用 sizeof 时,大小将始终为 4(或 8,或您的系统所做的任何事情)。

      MSFT 的匈牙利表示法可能很难看,但如果您使用它,就知道不要在任何以“p”开头的东西上调用宏。

      还要检查 WinNT.h 中 ARRAYSIZE() 宏的定义。如果你使用 C++,你可以用模板做一些奇怪的事情来获得编译时断言,如果这样做的话。

      【讨论】:

      • 你可以在指针上调用sizeof(),但是你得到的大小将是指针的大小,而不是数组的大小。
      【解决方案6】:
      foo(int * arr) //Also tried foo(int arr[]), foo(int * & arr) 
      {              // - neither of which worked
        printf("arr : %x\n", arr);
        printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));
      }
      

      sizeof(arr) 是 sizeof(int*),即。 4

      除非您有充分的理由编写这样的代码,否则不要这样做。我们现在是 21 世纪,请改用 std::vector。

      有关详细信息,请参阅 C++ 常见问题解答:http://www.parashift.com/c++-faq-lite/containers.html

      记住:“数组是邪恶的”

      【讨论】:

      • 不应该是:“我们现在是 21 世纪,没有必要再使用 C++了”;-)
      • 你有更合适的吗...? :-)
      【解决方案7】:

      请注意,即使你试图告诉 C 编译器函数中数组的大小,它也不会接受提示(我的 DIM 相当于你的 G_N_ELEMENTS):

      #include <stdio.h>
      
      #define DIM(x)  (sizeof(x)/sizeof(*(x)))
      
      static void function(int array1[], int array2[4])
      {
          printf("array1: size = %u\n", (unsigned)DIM(array1));
          printf("array2: size = %u\n", (unsigned)DIM(array2));
      }
      
      int main(void)
      {
          int a1[40];
          int a2[4];
          function(a1, a2);
          return(0);
      }
      

      打印出来:

      array1: size = 1
      array2: size = 1
      

      如果您想知道函数内部的数组有多大,请将大小传递给函数。或者,在 C++ 中,使用 STL vector&lt;int&gt; 之类的东西。

      【讨论】:

      • +1。事实上,至少 C++(也许是 C,我不确定)不尊重给定的大小,甚至给出警告/错误,恕我直言,这是相当应受谴责的。
      • 是的,我自己只是想知道 sizeof(complete-array-type parameters)。
      【解决方案8】:

      如果你稍微改变一下 foo 函数,它可能会让你感觉更舒服一点:

      void foo(int * pointertofoo) 
      {              
         printf("pointertofoo : %x\n", pointertofoo);  
         printf ("sizeof pointertofoo: %d\n", G_N_ELEMENTS(pointertofoo));
      }
      

      这就是编译器会看到与函数完全不同的上下文。

      【讨论】:

        【解决方案9】:

        这不起作用,因为 sizeof 是在编译时计算的。该函数没有关于其参数大小的信息(它只知道它指向一个内存地址)。

        考虑改用 STL 向量,或将数组大小作为参数传递给函数。

        【讨论】:

          猜你喜欢
          • 2013-10-21
          • 2018-08-26
          • 1970-01-01
          • 2015-02-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多