在C中查找字符数组的保留内存大小

我正在努力学习C,作为一个开始,我开始为自己的练习写一个strcpy。 我们知道,原来的strcpy容易出现安全问题所以我给自己写了一个“安全”strcpy的任务。

我选择的路径是检查源字符串(字符数组)是否实际适合目标内存。 正如我所理解的,C中的字符串只不过是指向字符数组的指针,0x00终止。

所以我的挑战是如何找到编译器实际为目标字符串保留了多少内存?

我试过了:

sizeof(dest) 

但这不起作用,因为它将返回(我后来发现)dest的大小,它实际上是一个指针,在我的64位机器上,将始终返回8。

我也尝试过:

 strlen(dest) 

但这不起作用,因为它只会返回长度,直到遇到第一个0x0,这不一定反映保留的实际内存。

所以这一切总结为以下问题:如何找到编译器为我的目标“字符串”保留了多少内存?

例:

 char s[80] = ""; int i = someFunction(s); // should return 80 

什么是“someFunction”?

提前致谢!

您可以在编译时使用sizeof进行检查:

 char s[80] = ""; int i = sizeof s ; // should return 80 

请注意,如果s是指针,则会失败:

 char *s = ""; int j = sizeof s; /* probably 4 or 8. */ 

数组不是指针。 为了跟踪为指针分配的大小,程序必须简单地跟踪它。 此外,您无法将数组传递给函数。 当您使用数组作为函数的参数时,编译器会将其转换为指向第一个元素的指针,因此,如果您希望该大小可用于被调用函数,则必须将其作为参数传递。 例如:

 char s[ SIZ ] = ""; foo( s, sizeof s ); 

一旦将char指针传递给您正在编写的函数,您就会忘记分配给s的内存量。 您需要将此大小作为参数传递给函数。

所以这一切总结为以下问题:如何找到编译器为我的目标“字符串”保留了多少内存?

没有可移植的方法来查明分配了多少内存。 你必须自己跟踪它。

实现必须跟踪多少内存被指向一个指针,它可能会让你找到一些东西。 例如,glibc的malloc.h公开了

 size_t malloc_usable_size (void *__ptr) 

这使您可以访问大致相同的信息,但是,它不会告诉您您请求了多少,但可以使用多少。 当然,这只适用于从malloc (和朋友)获得的指针。 对于数组,只能使用sizeof数组本身在范围内。

 char s[80] = ""; int i = someFunction(s); // should return 80 

在表达式中, s是指向数组s的第一个元素的指针。 您不能使用指向其第一个元素的指针值的唯一信息来推断数组对象的大小。 您可以做的唯一事情是在声明数组(此处为sizeof s )后存储数组大小的信息,然后将此信息传递给需要它的函数。

没有可移植的方法来做到这一点。 但是,实现当然需要在内部了解这些信息。 基于Unix的操作系统,如Linux和OS X,为此任务提供了以下function:

 // OS X #include  size_t allocated = malloc_size(somePtr); // Linux #include  size_t allocated = malloc_usable_size(somePtr); // Maybe Windows... size_t allocated = _msize(somePtr); 

标记malloc返回的成员的方法是始终malloc一个额外的sizeof(size_t)字节。 将其添加到malloc返回的地址,并且您有一个存储空间用于存储实际长度。 存储malloced大小 – sizeof(size_t),你有新的function集。

当你将这两种指针传递给新的特殊strcpy时,你可以从指针中减去sizeof(size_t),并直接访问这些尺寸。 这可以让您决定是否可以安全地复制内存。

如果您正在执行strcat,那么这两个大小以及计算strlens意味着您可以执行相同类型的检查以查看strcat的结果是否会溢出内存。

这是可行的。 它可能比它的价值更麻烦。

考虑如果传入未被mallocated的字符指针会发生什么。 假设大小在指针之前。 这种假设是错误的。 在这种情况下尝试访问大小是未定义的行为。 如果幸运的话,你可能会收到一个信号。

这种实现的另一个含义是当你去释放内存时,你必须传入完全指向那个malloc返回的指针。 如果你没有做到这一点,可能会发生堆损坏。

长话短说……不要这样做。

对于在程序中使用字符缓冲区的情况,可以执行一些冒烟和镜像以获得所需的效果。 像这样的东西。

 char input[] = "test"; char output[3]; if (sizeof(output) < sizeof(input)) { memcpy(output,input,sizeof(input) + 1); } else { printf("Overflow detected value <%s>\n",input); } 

可以通过在宏中包装代码来改进错误消息。

 #define STRCPYX(output,input) \ if (sizeof(output) < sizeof(input)) \ { \ memcpy(output,input,sizeof(input) + 1); \ } \ else \ { \ printf("STRCPYX would overflow %s with value <%s> from %s\n", \ #output, input, #input); \ } \ char input[] = "test"; char output[3]; STRCPYX(output,input); 

虽然这确实可以满足您的需求,但同样的风险也适用。

 char *input = "testing 123 testing"; char output[9]; STRCPYX(output,input); 

输入的大小为8,输出为9,输出值最终为“Testing”

C不是为了保护程序员不正确地做事而设计的。 这有点像你试图划船上游:)这是一个很好的锻炼思考。

虽然数组和指针看起来可以互换,但它们在一个重要方面有所不同; 数组有大小。 但是,因为传递给函数的数组“降级”为指针,所以大小信息会丢失。

关键是在某些时候知道对象的大小 – 因为你分配它或声明它是一定的大小。 C语言使您有责任在必要时保留和传播该信息。 所以在你的例子之后:

 char s[80] = ""; // sizeof(s) here is 80, because an array has size int i = someFunction(s, sizeof(s)) ; // You have to tell the function how big the array is. 

someFunction()没有确定数组大小的“神奇”方法,因为该信息被丢弃(出于性能和效率的原因 – 在这方面C是相对较低的水平,并且不添加代码或数据不明确); 如果需要这些信息,您必须明确传递它。

您可以传递字符串并保留大小信息,甚至通过复制而不是通过引用传递字符串的一种方法是将字符串包装在结构中:

 typedef struct { char s[80] ; } charArray_t ; 

然后

 charArray_t s ; int i = someFunction( &s ) ; 

someFunction()的定义类似:

 int someFunction( charArray_t* s ) { return sizeof( s->s ) ; } 

然而,通过这样做你并没有真正获得多少 – 只需避免额外的参数; 实际上你失去了一些灵活性,因为someFunction()现在只采用charrArray_t定义的固定数组长度,而不是任何数组。 有时这种限制很有用。 这种方法的特点是你可以pass by copy这个:

 int i = someFunction( s ) ; 

然后

 int someFunction( charArray_t s ) { return sizeof( ss ) ; } 

因为与数组不同的结构可以通过这种方式传递 您也可以通过副本同样返回。 然而,它可能有些低效。 然而,有时方便性和安全性超过低效率。