【问题标题】:How to find the 'sizeof' (a pointer pointing to an array)?如何找到“sizeof”(指向数组的指针)?
【发布时间】:2021-11-28 06:24:26
【问题描述】:

首先,这里有一些代码:

int main() 
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", sizeof(days));
    printf("%u\n", sizeof(ptr));

    return 0;
}

有没有办法找出ptr 指向的数组的大小(而不是仅仅给出它的大小,在 32 位系统上是四个字节)?

【问题讨论】:

  • 我一直使用带有 sizeof 的括号 - 确保它看起来像一个函数调用,但我认为它更清晰。
  • 为什么不呢?你有反对多余括号的东西吗?我认为我自己更容易阅读它们。
  • @Paul:好吧..假设该调用的左侧是指向 int 的指针,我将其写为 int *ptr = malloc(4 * sizeof *ptr);这对我来说更清楚。少读括号,将字面常量放在前面,就像在数学中一样。
  • @unwind - 当你的意思是整数数组时,不要分配指针数组!
  • 这里没有“指向数组的指针”。只是一个指向 int 的指针。

标签: c arrays pointers sizeof


【解决方案1】:

不,你不能。编译器不知道指针指向什么。有一些技巧,比如用一个已知的带外值结束数组,然后计算大小直到该值,但这不是使用sizeof()

另一个技巧是Zan 提到的一个技巧,就是将大小存储在某个地方。例如,如果您正在动态分配数组,请分配一个比您需要的大一个 int 的块,将大小存储在第一个 int 中,然后返回 ptr+1 作为指向数组的指针。当您需要大小时,减小指针并查看隐藏的值。请记住从头开始释放整个块,而不仅仅是数组。

【讨论】:

  • 很抱歉这么晚才发表评论,但是如果编译器不知道指针指向什么,那么 free 如何知道要清除多少内存?我确实知道这些信息是在内部存储的,用于免费使用等功能。所以我的问题是为什么编译器也可以这样做?
  • @viki.omega9,因为 free 在运行时发现大小。编译器无法知道大小,因为您可以根据运行时因素(命令行参数、文件内容、月相等)使数组大小不同。
  • 快速跟进,为什么没有一个可以像free一样返回大小的函数?
  • 好吧,如果你能保证函数只使用分配的内存调用,并且库以我见过的大多数方式跟踪分配的内存(通过在返回的指针之前使用 int)那么你可以写一个。但如果指针指向静态数组等,它就会失败。同样,不能保证您的程序可以访问 malloced 内存的大小。
  • @viki.omega9:另外要记住的是,malloc/free 系统记录的大小可能不是你要求的大小。你 malloc 9 个字节得到 16 个。malloc 3K 字节得到 4K。或类似情况。
【解决方案2】:

答案是“不”。

C 程序员所做的是将数组的大小存储在某处。它可以是结构的一部分,或者程序员可以作弊并malloc() 比请求更多的内存,以便在数组开始之前存储长度值。

【讨论】:

  • 这就是pascal字符串的实现方式
  • 显然帕斯卡字符串是 excel 运行如此之快的原因!
  • @Adam:很快。我在我的字符串实现列表中使用它。线性搜索非常快,因为它是:加载大小,预取 pos+size,将大小与搜索大小进行比较,如果等于 strncmp,则移动到下一个字符串,重复。它比最多约 500 个字符串的二进制搜索要快。
【解决方案3】:

对于动态数组(malloc 或 C++ new),您需要存储其他人提到的数组的大小,或者可能构建一个处理添加的数组管理器结构,删除、计数等。不幸的是,C 在这方面的表现几乎不如 C++,因为您基本上必须为要存储的每种不同的数组类型构建它,如果您需要管理多种类型的数组,这会很麻烦。

对于静态数组,例如您的示例中的数组,有一个通用宏用于获取大小,但不推荐,因为它不检查参数是否真的是静态的大批。不过,宏在实际代码中使用,例如在 Linux 内核头文件中,尽管它可能与下面的略有不同:

#if !defined(ARRAY_SIZE)
    #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
#endif

int main()
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", ARRAY_SIZE(days));
    printf("%u\n", sizeof(ptr));
    return 0;
}

您可以出于对此类宏保持警惕的原因进行谷歌搜索。小心点。

如果可能的话,C++标准库,比如vector,它更安全,更容易使用。

【讨论】:

  • ARRAY_SIZE 是世界各地实际程序员使用的通用范式。
  • 是的,这是一个常见的范例。您仍然需要谨慎使用它,因为它很容易忘记并将其用于动态数组。
  • 是的,好点,但被问到的问题是关于指针一,而不是静态数组一。
  • 如果 ARRAY_SIZE 宏的参数是一个数组(即数组类型的表达式),它总是有效的。对于您所谓的“动态数组”,您永远不会得到实际的“数组”(数组类型的表达式)。 (当然,你不能,因为数组类型在编译时包括它们的大小。)你只需要一个指向第一个元素的指针。您的反对“不检查参数是否真的是静态数组”并不是真正有效的,因为它们是不同的,因为一个是数组而另一个不是。
  • 有一个模板函数浮动,它做同样的事情,但会阻止使用指针。
【解决方案4】:

有一个使用 C++ 模板的干净解决方案,无需使用 sizeof()。以下 getSize() 函数返回任何静态数组的大小:

#include <cstddef>

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

这是一个具有 foo_t 结构的示例:

#include <cstddef>

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

struct foo_t {
    int ball;
};

int main()
{
    foo_t foos3[] = {{1},{2},{3}};
    foo_t foos5[] = {{1},{2},{3},{4},{5}};
    printf("%u\n", getSize(foos3));
    printf("%u\n", getSize(foos5));

    return 0;
}

输出:

3
5

【讨论】:

  • 我从未见过T (&amp;)[SIZE]这个符号。你能解释一下这是什么意思吗?您也可以在这种情况下提及 constexpr。
  • 如果您使用 c++ 并且您实际上有一个数组类型的变量,那就太好了。问题中的情况都不是这样:语言是 C,OP 想要从中获取数组大小的东西是一个简单的指针。
  • 此代码是否会通过为每种不同的大小/类型组合重新创建相同的代码而导致代码膨胀,或者编译器是否已神奇地优化不存在?
  • @WorldSEnder:这是数组类型引用的 C++ 语法(没有变量名,只有大小和元素类型)。
  • @user2796283:这个函数在编译时被完全优化掉了;不需要魔法;它没有将任何东西组合到一个定义中,它只是将它内联到一个编译时常量。 (但是在调试版本中,是的,你会有一堆返回不同常量的单独函数。链接器魔法可能会合并使用相同常量的函数。调用者不会将 SIZE 作为 arg 传递,它是一个模板函数定义必须已经知道的参数。)
【解决方案5】:

正如所有正确答案所述,您无法仅从数组的衰减指针值中获取此信息。如果衰减的指针是函数接收的参数,则必须以其他方式提供原始数组的大小,以便函数知道该大小。

这里有一个与迄今为止提供的不同的建议,它会起作用:改为传递指向数组的指针。此建议类似于 C++ 样式建议,只是 C 不支持模板或引用:

#define ARRAY_SZ 10

void foo (int (*arr)[ARRAY_SZ]) {
    printf("%u\n", (unsigned)sizeof(*arr)/sizeof(**arr));
}

但是,这个建议对您的问题来说有点愚蠢,因为该函数被定义为准确知道传入的数组的大小(因此,几乎不需要在数组上使用 sizeof)。不过,它所做的是提供某种类型的安全性。它会禁止你传入一个不需要的大小的数组。

int x[20];
int y[10];
foo(&x); /* error */
foo(&y); /* ok */

如果函数应该能够对任何大小的数组进行操作,那么您必须将大小作为附加信息提供给函数。

【讨论】:

    【解决方案6】:

    对于这个具体的例子,是的,如果你使用 typedefs(见下文)。当然,如果你这样做,你也可以使用 SIZEOF_DAYS,因为你知道指针指向什么。

    如果你有一个 (void *) 指针,如 malloc() 等返回的那样,那么,不,没有办法确定指针指向的数据结构,因此,无法确定它的大小。

    #include <stdio.h>
    
    #define NUM_DAYS 5
    typedef int days_t[ NUM_DAYS ];
    #define SIZEOF_DAYS ( sizeof( days_t ) )
    
    int main() {
        days_t  days;
        days_t *ptr = &days; 
    
        printf( "SIZEOF_DAYS:  %u\n", SIZEOF_DAYS  );
        printf( "sizeof(days): %u\n", sizeof(days) );
        printf( "sizeof(*ptr): %u\n", sizeof(*ptr) );
        printf( "sizeof(ptr):  %u\n", sizeof(ptr)  );
    
        return 0;
    } 
    

    输出:

    SIZEOF_DAYS:  20
    sizeof(days): 20
    sizeof(*ptr): 20
    sizeof(ptr):  4
    

    【讨论】:

      【解决方案7】:

      没有神奇的解决方案。 C 不是反射语言。对象不会自动知道它们是什么。

      但是你有很多选择:

      1. 显然,添加一个参数
      2. 将调用包装在宏中并自动添加参数
      3. 使用更复杂的对象。定义一个包含动态数组以及数组大小的结构。然后,传递结构的地址。

      【讨论】:

      • 对象知道它们是什么。但是,如果您指向一个子对象,则无法获取有关完整对象或更大子对象的信息
      【解决方案8】:

      你可以这样做:

      int days[] = { /*length:*/5, /*values:*/ 1,2,3,4,5 };
      int *ptr = days + 1;
      printf("array length: %u\n", ptr[-1]);
      return 0;
      

      【讨论】:

        【解决方案9】:

        我对这个问题的解决方案是将数组的长度保存到结构数组中,作为数组的元信息。

        #include <stdio.h>
        #include <stdlib.h>
        
        struct Array
        {
            int length;
        
            double *array;
        };
        
        typedef struct Array Array;
        
        Array* NewArray(int length)
        {
            /* Allocate the memory for the struct Array */
            Array *newArray = (Array*) malloc(sizeof(Array));
        
            /* Insert only non-negative length's*/
            newArray->length = (length > 0) ? length : 0;
        
            newArray->array = (double*) malloc(length*sizeof(double));
        
            return newArray;
        }
        
        void SetArray(Array *structure,int length,double* array)
        {
            structure->length = length;
            structure->array = array;
        }
        
        void PrintArray(Array *structure)
        {       
            if(structure->length > 0)
            {
                int i;
                printf("length: %d\n", structure->length);
                for (i = 0; i < structure->length; i++)
                    printf("%g\n", structure->array[i]);
            }
            else
                printf("Empty Array. Length 0\n");
        }
        
        int main()
        {
            int i;
            Array *negativeTest, *days = NewArray(5);
        
            double moreDays[] = {1,2,3,4,5,6,7,8,9,10};
        
            for (i = 0; i < days->length; i++)
                days->array[i] = i+1;
        
            PrintArray(days);
        
            SetArray(days,10,moreDays);
        
            PrintArray(days);
        
            negativeTest = NewArray(-5);
        
            PrintArray(negativeTest);
        
            return 0;
        }
        

        但是你必须关心设置你想要存储的数组的正确长度,因为没有办法检查这个长度,就像我们的朋友大量解释的那样。

        【讨论】:

          【解决方案10】:

          不,您不能使用sizeof(ptr) 来查找ptr 指向的数组的大小。

          如果您想将长度存储在额外的空间中,分配额外的内存(超过数组的大小)会很有帮助。

          【讨论】:

            【解决方案11】:
            int main() 
            {
                int days[] = {1,2,3,4,5};
                int *ptr = days;
                printf("%u\n", sizeof(days));
                printf("%u\n", sizeof(ptr));
            
                return 0;
            }
            

            days[] 的大小为 20,即元素数 * 数据类型的大小。 而指针的大小是 4 无论它指向什么。 因为指针通过存储它的地址来指向其他元素。

            【讨论】:

            • sizeof(ptr) 是指针的大小,sizeof(*ptr) 是指针的大小
            【解决方案12】:

            这就是我个人在代码中的做法。我喜欢让它尽可能简单,同时仍然能够获得我需要的值。

            typedef struct intArr {
                int size;
                int* arr; 
            } intArr_t;
            
            int main() {
                intArr_t arr;
                arr.size = 6;
                arr.arr = (int*)malloc(sizeof(int) * arr.size);
            
                for (size_t i = 0; i < arr.size; i++) {
                    arr.arr[i] = i * 10;
                }
            
                return 0;
            }
            

            【讨论】:

            • 首选size_t来存储大小。
            【解决方案13】:
             #define array_size 10
            
             struct {
                 int16 size;
                 int16 array[array_size];
                 int16 property1[(array_size/16)+1]
                 int16 property2[(array_size/16)+1]
             } array1 = {array_size, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
            
             #undef array_size
            

            array_size 正在传递给 size 变量:

            #define array_size 30
            
            struct {
                int16 size;
                int16 array[array_size];
                int16 property1[(array_size/16)+1]
                int16 property2[(array_size/16)+1]
            } array2 = {array_size};
            
            #undef array_size
            

            用法是:

            void main() {
            
                int16 size = array1.size;
                for (int i=0; i!=size; i++) {
            
                    array1.array[i] *= 2;
                }
            }
            

            【讨论】:

              【解决方案14】:

              在字符串的末尾有一个'\0' 字符,因此可以使用strlen 之类的函数来获取字符串的长度。例如,整数数组的问题在于,您不能使用任何值作为结束值,因此一种可能的解决方案是寻址数组并将NULL 指针用作结束值。

              #include <stdio.h>
              /* the following function will produce the warning:
               * ‘sizeof’ on array function parameter ‘a’ will
               * return size of ‘int *’ [-Wsizeof-array-argument]
               */
              void foo( int a[] )
              {
                  printf( "%lu\n", sizeof a );
              }
              /* so we have to implement something else one possible
               * idea is to use the NULL pointer as a control value
               * the same way '\0' is used in strings but this way
               * the pointer passed to a function should address pointers
               * so the actual implementation of an array type will
               * be a pointer to pointer
               */
              typedef char * type_t; /* line 18 */
              typedef type_t ** array_t;
              int main( void )
              {
                  array_t initialize( int, ... );
                  /* initialize an array with four values "foo", "bar", "baz", "foobar"
                   * if one wants to use integers rather than strings than in the typedef
                   * declaration at line 18 the char * type should be changed with int
                   * and in the format used for printing the array values 
                   * at line 45 and 51 "%s" should be changed with "%i"
                   */
                  array_t array = initialize( 4, "foo", "bar", "baz", "foobar" );
              
                  int size( array_t );
                  /* print array size */
                  printf( "size %i:\n", size( array ));
              
                  void aprint( char *, array_t );
                  /* print array values */
                  aprint( "%s\n", array ); /* line 45 */
              
                  type_t getval( array_t, int );
                  /* print an indexed value */
                  int i = 2;
                  type_t val = getval( array, i );
                  printf( "%i: %s\n", i, val ); /* line 51 */
              
                  void delete( array_t );
                  /* free some space */
                  delete( array );
              
                  return 0;
              }
              /* the output of the program should be:
               * size 4:
               * foo
               * bar
               * baz
               * foobar
               * 2: baz
               */
              #include <stdarg.h>
              #include <stdlib.h>
              array_t initialize( int n, ... )
              {
                  /* here we store the array values */
                  type_t *v = (type_t *) malloc( sizeof( type_t ) * n );
                  va_list ap;
                  va_start( ap, n );
                  int j;
                  for ( j = 0; j < n; j++ )
                      v[j] = va_arg( ap, type_t );
                  va_end( ap );
                  /* the actual array will hold the addresses of those
                   * values plus a NULL pointer
                   */
                  array_t a = (array_t) malloc( sizeof( type_t *) * ( n + 1 ));
                  a[n] = NULL;
                  for ( j = 0; j < n; j++ )
                      a[j] = v + j;
                  return a;
              }
              int size( array_t a )
              {
                  int n = 0;
                  while ( *a++ != NULL )
                      n++;
                  return n;
              }
              void aprint( char *fmt, array_t a )
              {
                  while ( *a != NULL )
                      printf( fmt, **a++ );   
              }
              type_t getval( array_t a, int i )
              {
                  return *a[i];
              }
              void delete( array_t a )
              {
                  free( *a );
                  free( a );
              }
              

              【讨论】:

              • 您的代码中充满了 cmets,但我认为,如果您在代码之外添加一些关于其工作原理的一般性解释(作为普通文本),一切都会变得更容易。你能请edit你的问题吗?谢谢!
              • 创建一个指向每个元素的指针数组,这样您就可以对其进行线性搜索以查找NULL,这可能是可以想象的效率最低的替代方法,而不是直接存储一个单独的size。尤其是如果您实际上一直使用这个额外的间接层。
              【解决方案15】:

              大多数实现都会有一个函数告诉您使用malloc()calloc() 分配的对象的保留大小,例如GNU 有malloc_usable_size()

              但是,这将返回反转块的大小,它可以大于给malloc()/realloc() 的值。


              【讨论】:

                【解决方案16】:

                有一个流行的宏,您可以定义它来查找数组中的元素数量(Microsoft CRT 甚至为它提供了名称为 _countof 的 OOB):

                #define countof(x) (sizeof(x)/sizeof((x)[0]))
                

                然后你可以写:

                int my_array[] = { ... some elements ... };
                printf("%zu", countof(my_array)); // 'z' is correct type specifier for size_t
                

                【讨论】:

                  猜你喜欢
                  相关资源
                  最近更新 更多