4

我正在尝试学习 C,作为开始,我开始为自己的练习编写一个 strcpy。众所周知,原始的 strcpy 很容易出现安全问题,所以我给自己写了一个“安全”的 strcpy 的任务。

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

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

我试过:

sizeof(dest)

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

我也试过:

strlen(dest)

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

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

例子:

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

什么是“一些功能”?

提前致谢!

4

8 回答 8

4

一旦你将一个 char 指针传递给你正在编写的函数,你就会失去关于分配给 s 多少内存的知识。您需要将此大小作为参数传递给函数。

于 2013-01-27T14:07:53.813 回答
2

您可以使用 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 );
于 2013-01-27T14:08:48.210 回答
2

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

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

实现必须跟踪指针有多少内存malloc,它可能会为您提供一些可用的信息。例如 glibc 的malloc.h暴露

size_t malloc_usable_size (void *__ptr)

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

于 2013-01-27T14:13:02.927 回答
1
char s[80] = "";
int i = someFunction(s); // should return 80

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

于 2013-01-27T14:11:19.683 回答
1

没有便携的方法可以做到这一点。但是,实现肯定需要在内部知道这些信息。基于 Unix 的操作系统,如 Linux 和 OS X,为此任务提供了功能:

// OS X
#include <malloc/malloc.h>

size_t allocated = malloc_size(somePtr);

// Linux
#include <malloc.h>

size_t allocated = malloc_usable_size(somePtr);


// Maybe Windows...

size_t allocated = _msize(somePtr);
于 2013-01-27T14:13:34.673 回答
0

标记 malloc 返回的成员的一种方法是始终 malloc 一个额外的 sizeof(size_t) 字节。将其添加到 malloc 返回的地址,您就有了存储实际长度的存储空间。将分配的大小 - sizeof (size_t) 存储在那里,您就有了新功能集的基础。

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

如果你在做 strcat,那么这两个大小以及计算 strlens 意味着你可以做同样的检查,看看 strcat 的结果是否会溢出内存。

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

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

这种实现的另一个含义是,当您释放内存时,您必须传入完全返回的 malloc 指针。如果你没有得到正确的,堆损坏是可能的。

长话短说...不要那样做。

于 2013-01-27T14:47:10.873 回答
0

对于在程序中使用字符缓冲区的情况,您可以做一些烟雾和镜子来获得您想要的效果。像这样的东西。

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 并不是为了保护程序员不做错误的事情而设计的。这有点像您正试图在上游划桨 :) 这是一个很好的练习。

于 2013-01-27T15:07:41.893 回答
0

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

关键是在某些时候知道对象的大小 - 因为您分配了它或将其声明为某个大小。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( s.s ) ; 
}

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

于 2013-01-27T16:05:10.413 回答