将 char 指针分配给运行时生成的字符串文字 - 这是动态分配吗?
Assigning a char pointer to string literal generated at runtime - Is this dynamic allocation?
我正在审查别人写的一些代码。我在这段代码中遇到了一个涉及字符串的有趣案例,我需要帮助来理解它是如何工作的。
有一个函数设计用于导出到 DLL。在函数的顶部,我们有这个声明
char *msg; // pointer to char
int error = 0; //my error code
然后,在代码的后面,我们为我们使用的 IDE 调用一个特殊的库函数:
if(error < 0)
msg = getErrorString(error);
这个内置库函数 (getErrorString) 希望您提供一个指向 char 的指针,它可以在运行时存储生成的错误字符串。
最后,代码作者调用如下:
free(msg); // freeing dynamically allocated memory??
所以,我猜想在运行时动态分配了足够大的内存来存储生成的错误字符串?如果不显式调用诸如 malloc 之类的东西,这是如何允许的?如果我正在编写等效代码,我的第一直觉是声明一些静态数组,如 msg[256],然后执行如下操作:
char msg[256] = {""};
sprintf(msg, "%s", getErrorString(error));
所以我的主要问题是,如何声明一个指向 char 的指针,然后将其分配给一个在运行时生成的字符串,如原始代码所示?似乎内存是在运行时动态分配的,可能是由运行时引擎分配的。这是这段代码发生的事情吗?在这种情况下我的静态数组方法是首选吗?
getErrorString
必须记录其调用 malloc
。通常认为编写 API 函数 return 指向已分配内存的指针然后期望其他人清理它是非常糟糕的做法。我们从 40 年的 C 语言历史中了解到,像这样设计不当的 API 正是造成内存泄漏的原因。
更好的 API 会提供显式 init/create 函数和显式 cleanup/delete 函数。这些函数在内部所做的是 none 调用者的业务 - 他们只需要确保在其他任何事情之前调用 init 并清理他们所做的最后一件事。
关于s****y API设计的话题,我希望一个函数getErrorString
到return一个const char*
,因为为什么会出错消息需要调用者修改?这应该是一个不可变的字符串。
How is this allowed without explicitly calling something like malloc?
好吧,几乎可以肯定的是,在 getErrorString
的实现中的某处, 是 对 malloc
或等效项的调用。
getErrorString
的实现很可能是这样的:
char *getErrorString(int error)
{
char *ret = malloc(25);
if(ret == NULL) abort();
switch(error) {
case EMUCHMEM:
strcpy(ret, "too much memory");
break;
case EIUNDERFLOW:
strcpy(ret, "integer underflow");
break;
case EDIVBY1:
strcpy(ret, "divide by 1");
break;
default:
sprintf(ret, "error %d", error);
break;
}
return ret;
}
If I were writing equivalent code, my first instinct would be to declare some static array like msg[256] and then do something like:
char msg[256] = {""};
sprintf(msg, "%s", getErrorString(error));
这没有多大意义。它表示不必要的额外内存(msg[]
中的 256 字节)和不必要的额外复制(sprintf
)。
So my main question is, how can you declare a pointer to char, then assign it to a string which is generated at runtime, as shown in the original code?
您可以始终声明一个指向char
的指针,然后为其分配一个在运行时生成的字符串。
不过,这可能是一个令人困惑的问题。您可能听说过字符串在 C 中表示为 char
的数组 — 这是真的 — 您可能还听说过您不能在 C 中分配数组 — 这也是真的。您可能听说过,您总是必须调用 strcpy
而不是直接分配字符串。但这 不一定 一定是真的——您也可以通过简单地分配指针来“分配”字符串,这就是您说 msg = getErrorString(error)
时发生的事情。换句话说,在 C 中有两种完全不同的字符串赋值方式:复制数组,或赋值指针。有关这一点的更多信息,请参阅 this other answer。
It seems that memory is being dynamically allocated at runtime
是的,看起来就是这样。
Is my static array method preferred in this case?
正如其他评论所建议的,动态内存分配在这种情况下可能是也可能不是一个好主意。不过,作为一般规则,动态内存分配是一种非常好的——或多或少是重要的——技术。另一方面,静态内存分配本身可能有很多问题。
根据您所描述的代码,getErrorString
显然正在调用 malloc
或 calloc
(或某些等效项)本身并 returning 该指针。这实际上是相当普遍的做法 - 请参阅 POSIX strdup
函数作为另一个示例。
要注意的是,您负责在您使用完内存后释放该内存。如果 getErrorString
动态分配内存,那么需要记录下来,这样任何使用它的人都会知道 free
当他们用完内存时。
If I were writing equivalent code, my first instinct would be to declare some static array like msg[256] and then do something like:
char msg[256] = {""};
sprintf(msg, "%s", getErrorString(error));
坏主意,因为您丢弃了由 getErrorString
编辑的指针值 return,这意味着您永远无法 free
它分配的内存。
getErrorString
正在为您分配所有必要的内存来存储字符串;您不需要留出自己的缓冲区来存储字符串本身。您只需要存储 returned 指针值,以便稍后 free
该内存。
如何处理动态分配的内存一直是API的一个棘手问题。 一般来说理想的设计是让负责内存分配的实体也负责释放内存;就个人而言,我会设计 getErrorString
来接收错误代码 和 指向目标缓冲区及其大小的指针:
char *getErrorString( int errorCode, char *buf, size_t bufSize )
{
switch( errorCode )
{
case SOME_ERROR:
strncpy( buf, "Error message for SOME_ERROR", bufSize );
break;
case SOME_OTHER_ERROR:
strncpy( buf, "Error message for SOME_OTHER_ERROR", bufSize );
break;
...
}
/**
* Make sure buf is properly 0-terminated, since strncpy won't
* zero-terminate if the target buffer is shorter than the
* source string
*/
buf[bufSize-1] = 0;
return buf;
}
这样 我 负责缓冲区的分配和释放。我可以使用 auto
数组,完全不用担心内存管理:
void foo( void )
{
char msg[81];
...
fprintf( stderr, "%s", getErrorString( error, msg, sizeof msg ) );
...
}
在这种情况下,错误信息被写入msg
; getErrorString
returns msg
的地址,所以它可以作为 fprintf
的一部分被调用;因为它是一个 auto
变量,msg
的内存将在函数退出时自动释放。
或者,如果我愿意,我可以动态分配该内存:
char *msg = calloc( 81, sizeof *msg );
...
fprintf( stderr, "%s\n", getErrorString( error, msg, 81 );
...
free( msg );
但无论哪种方式,分配和释放内存的责任都在同一个实体中(代码调用 getErrorString
);它不会在两个不同的实体之间拆分。
另一个选项是让函数维护静态内部缓冲区:
char *getErrorString( int error )
{
static char buf[SOME_SIZE+1]; // where SOME_SIZE is the length of the longest
// error string
switch( error )
{
case SOME_ERROR:
strcpy( buf, "Error string for SOME_ERROR" );
break;
case SOME_OTHER_ERROR:
strcpy( buf, "Error string for SOME_OTHER_ERROR" );
break;
...
}
return buf;
}
由于buf
被声明为static
,它的生命周期就是整个程序的生命周期,所以它不会在getErrorString
退出时消失。这样就没有人需要担心管理缓冲区的内存了。
这种方法的问题是 getErrorString
不再是 re-entrant 或 thread-safe - 你在整个程序中只有一个缓冲区,所以如果 getErrorString
被本身调用 getErrorString
的其他代码中断,或者如果两个线程同时调用 getErrorString
,则该缓冲区将被破坏。
作为最后的选择,如果所有的字符串都是常量,那么根本不需要预留任何内存 - 直接return字符串文字:
/**
* Attempting to modify a string literal invokes undefined behavior,
* so we don't want this pointer to be used as the target of
* a strcpy or sprintf call. We change the return value to const char *
* so the compiler will yell at us if we try to modify the pointed-to
* string.
*/
const char *getErrorString( int error )
{
switch( error )
{
case SOME_ERROR:
return "Error string for SOME_ERROR";
break;
case SOME_OTHER_ERROR:
return "Error string for SOME_OTHER_ERROR";
break;
...
}
return "Unknown error code";
}
现在我们可以直接调用该函数而不必担心:
fprintf( stderr, "%s\n", getErrorString( error ) );
如果您出于任何原因仍想留出内存来存储错误字符串,您可以:
const char *str = getErrorString( error );
char *buf = malloc( strlen( str ) + 1 );
if ( buf )
strcpy( buf, str );
或
char *buf = strdup( getErrorString( error ) );
我正在审查别人写的一些代码。我在这段代码中遇到了一个涉及字符串的有趣案例,我需要帮助来理解它是如何工作的。
有一个函数设计用于导出到 DLL。在函数的顶部,我们有这个声明
char *msg; // pointer to char
int error = 0; //my error code
然后,在代码的后面,我们为我们使用的 IDE 调用一个特殊的库函数:
if(error < 0)
msg = getErrorString(error);
这个内置库函数 (getErrorString) 希望您提供一个指向 char 的指针,它可以在运行时存储生成的错误字符串。
最后,代码作者调用如下:
free(msg); // freeing dynamically allocated memory??
所以,我猜想在运行时动态分配了足够大的内存来存储生成的错误字符串?如果不显式调用诸如 malloc 之类的东西,这是如何允许的?如果我正在编写等效代码,我的第一直觉是声明一些静态数组,如 msg[256],然后执行如下操作:
char msg[256] = {""};
sprintf(msg, "%s", getErrorString(error));
所以我的主要问题是,如何声明一个指向 char 的指针,然后将其分配给一个在运行时生成的字符串,如原始代码所示?似乎内存是在运行时动态分配的,可能是由运行时引擎分配的。这是这段代码发生的事情吗?在这种情况下我的静态数组方法是首选吗?
getErrorString
必须记录其调用 malloc
。通常认为编写 API 函数 return 指向已分配内存的指针然后期望其他人清理它是非常糟糕的做法。我们从 40 年的 C 语言历史中了解到,像这样设计不当的 API 正是造成内存泄漏的原因。
更好的 API 会提供显式 init/create 函数和显式 cleanup/delete 函数。这些函数在内部所做的是 none 调用者的业务 - 他们只需要确保在其他任何事情之前调用 init 并清理他们所做的最后一件事。
关于s****y API设计的话题,我希望一个函数getErrorString
到return一个const char*
,因为为什么会出错消息需要调用者修改?这应该是一个不可变的字符串。
How is this allowed without explicitly calling something like malloc?
好吧,几乎可以肯定的是,在 getErrorString
的实现中的某处, 是 对 malloc
或等效项的调用。
getErrorString
的实现很可能是这样的:
char *getErrorString(int error)
{
char *ret = malloc(25);
if(ret == NULL) abort();
switch(error) {
case EMUCHMEM:
strcpy(ret, "too much memory");
break;
case EIUNDERFLOW:
strcpy(ret, "integer underflow");
break;
case EDIVBY1:
strcpy(ret, "divide by 1");
break;
default:
sprintf(ret, "error %d", error);
break;
}
return ret;
}
If I were writing equivalent code, my first instinct would be to declare some static array like msg[256] and then do something like:
char msg[256] = {""};
sprintf(msg, "%s", getErrorString(error));
这没有多大意义。它表示不必要的额外内存(msg[]
中的 256 字节)和不必要的额外复制(sprintf
)。
So my main question is, how can you declare a pointer to char, then assign it to a string which is generated at runtime, as shown in the original code?
您可以始终声明一个指向char
的指针,然后为其分配一个在运行时生成的字符串。
不过,这可能是一个令人困惑的问题。您可能听说过字符串在 C 中表示为 char
的数组 — 这是真的 — 您可能还听说过您不能在 C 中分配数组 — 这也是真的。您可能听说过,您总是必须调用 strcpy
而不是直接分配字符串。但这 不一定 一定是真的——您也可以通过简单地分配指针来“分配”字符串,这就是您说 msg = getErrorString(error)
时发生的事情。换句话说,在 C 中有两种完全不同的字符串赋值方式:复制数组,或赋值指针。有关这一点的更多信息,请参阅 this other answer。
It seems that memory is being dynamically allocated at runtime
是的,看起来就是这样。
Is my static array method preferred in this case?
正如其他评论所建议的,动态内存分配在这种情况下可能是也可能不是一个好主意。不过,作为一般规则,动态内存分配是一种非常好的——或多或少是重要的——技术。另一方面,静态内存分配本身可能有很多问题。
根据您所描述的代码,getErrorString
显然正在调用 malloc
或 calloc
(或某些等效项)本身并 returning 该指针。这实际上是相当普遍的做法 - 请参阅 POSIX strdup
函数作为另一个示例。
要注意的是,您负责在您使用完内存后释放该内存。如果 getErrorString
动态分配内存,那么需要记录下来,这样任何使用它的人都会知道 free
当他们用完内存时。
If I were writing equivalent code, my first instinct would be to declare some static array like msg[256] and then do something like:
char msg[256] = {""}; sprintf(msg, "%s", getErrorString(error));
坏主意,因为您丢弃了由 getErrorString
编辑的指针值 return,这意味着您永远无法 free
它分配的内存。
getErrorString
正在为您分配所有必要的内存来存储字符串;您不需要留出自己的缓冲区来存储字符串本身。您只需要存储 returned 指针值,以便稍后 free
该内存。
如何处理动态分配的内存一直是API的一个棘手问题。 一般来说理想的设计是让负责内存分配的实体也负责释放内存;就个人而言,我会设计 getErrorString
来接收错误代码 和 指向目标缓冲区及其大小的指针:
char *getErrorString( int errorCode, char *buf, size_t bufSize )
{
switch( errorCode )
{
case SOME_ERROR:
strncpy( buf, "Error message for SOME_ERROR", bufSize );
break;
case SOME_OTHER_ERROR:
strncpy( buf, "Error message for SOME_OTHER_ERROR", bufSize );
break;
...
}
/**
* Make sure buf is properly 0-terminated, since strncpy won't
* zero-terminate if the target buffer is shorter than the
* source string
*/
buf[bufSize-1] = 0;
return buf;
}
这样 我 负责缓冲区的分配和释放。我可以使用 auto
数组,完全不用担心内存管理:
void foo( void )
{
char msg[81];
...
fprintf( stderr, "%s", getErrorString( error, msg, sizeof msg ) );
...
}
在这种情况下,错误信息被写入msg
; getErrorString
returns msg
的地址,所以它可以作为 fprintf
的一部分被调用;因为它是一个 auto
变量,msg
的内存将在函数退出时自动释放。
或者,如果我愿意,我可以动态分配该内存:
char *msg = calloc( 81, sizeof *msg );
...
fprintf( stderr, "%s\n", getErrorString( error, msg, 81 );
...
free( msg );
但无论哪种方式,分配和释放内存的责任都在同一个实体中(代码调用 getErrorString
);它不会在两个不同的实体之间拆分。
另一个选项是让函数维护静态内部缓冲区:
char *getErrorString( int error )
{
static char buf[SOME_SIZE+1]; // where SOME_SIZE is the length of the longest
// error string
switch( error )
{
case SOME_ERROR:
strcpy( buf, "Error string for SOME_ERROR" );
break;
case SOME_OTHER_ERROR:
strcpy( buf, "Error string for SOME_OTHER_ERROR" );
break;
...
}
return buf;
}
由于buf
被声明为static
,它的生命周期就是整个程序的生命周期,所以它不会在getErrorString
退出时消失。这样就没有人需要担心管理缓冲区的内存了。
这种方法的问题是 getErrorString
不再是 re-entrant 或 thread-safe - 你在整个程序中只有一个缓冲区,所以如果 getErrorString
被本身调用 getErrorString
的其他代码中断,或者如果两个线程同时调用 getErrorString
,则该缓冲区将被破坏。
作为最后的选择,如果所有的字符串都是常量,那么根本不需要预留任何内存 - 直接return字符串文字:
/**
* Attempting to modify a string literal invokes undefined behavior,
* so we don't want this pointer to be used as the target of
* a strcpy or sprintf call. We change the return value to const char *
* so the compiler will yell at us if we try to modify the pointed-to
* string.
*/
const char *getErrorString( int error )
{
switch( error )
{
case SOME_ERROR:
return "Error string for SOME_ERROR";
break;
case SOME_OTHER_ERROR:
return "Error string for SOME_OTHER_ERROR";
break;
...
}
return "Unknown error code";
}
现在我们可以直接调用该函数而不必担心:
fprintf( stderr, "%s\n", getErrorString( error ) );
如果您出于任何原因仍想留出内存来存储错误字符串,您可以:
const char *str = getErrorString( error );
char *buf = malloc( strlen( str ) + 1 );
if ( buf )
strcpy( buf, str );
或
char *buf = strdup( getErrorString( error ) );