【问题标题】:why char array is empty in C [duplicate]为什么char数组在C中为空[重复]
【发布时间】:2024-04-12 04:05:03
【问题描述】:

我的代码如下:

char* int2str(int val);

void main(){
 char *s = int2str(1001);
 printf("----s=%s\n",s);
}

char* int2str(int val){
  char turnStr[10];
  sprintf(turnStr, "%d", val);
  //printf("turnStr=%s\n",turnStr);
  return turnStr;
}

上面的代码打印出空字符串,但是当我取消注释时:printf("turnStr=%s\n",turnStr) 它能够打印出正确的字符串。 我知道函数结束时堆栈空间无法返回,但是我很困惑当我添加printf("turnStr=%s\n",turnStr)时,它可以打印出字符串。

【问题讨论】:

  • 你对sprintf 的包装实际上在这里是一个巨大的负担。如果必须,请直接使用sprintf,或者改为使用printf
  • 令人惊讶的是人们没有很好地阅读这个问题。你的问题是“为什么 它起作用了?”而其他人都回答“为什么它不起作用?”。
  • @DavidG。这取决于你对“工作”的定义
  • @M.M 在这种情况下,输出“----s=1001”的行是“工作”。他问为什么会出现这种情况,而不是在我的一个案例中,添加了 C 转义符,“-----s=\x98\x06@”
  • 这(为什么会起作用)是 C 标签下方最常见的常见问题解答之一,但有 5 人发布了答案……有些答案甚至没有回答问题。对于那些不知道的人,我们有一组规范的欺骗目标,您可以/应该在这些情况下使用。检查C tag wiki,向下滚动到常见问题解答。

标签: c scope undefined-behavior c-strings storage-duration


【解决方案1】:

太棒了!

基本问题是你返回了栈上某物的地址,而它被其他东西改变了。我尝试了最近的 gcc,它甚至没有返回堆栈指针,所以我尝试了 gcc 4.4.5 并重现了您的行为。

我尝试将 main 更改为:

void main(){
 char *s = int2str(1001);
 printf("----s=%s\n",s);
 s = int2str(1002);
 printf("----s=%s\n",s);
}

第二个 printf() 输出 1002。

我认为发生的情况是 printf 有一些局部变量与您的数组放置在同一位置,如果您之前调用过 printf(),则不会使用这些局部变量。

请注意,它打印的不是空的,而是垃圾。这些垃圾可能以 NUL 开头,也可能不是。

无论如何,其他人都是对的,您不应该这样做。有多种解决方案,包括:

  1. 动态内存分配(这意味着您需要释放它)
  2. 传入缓冲区(添加参数...您应该传入长度)
  3. 使用静态缓冲区(线程或多次使用有问题)
  4. 按包含文本的值返回结构(可以复制超出应有的数量,这可能会导致性能问题,并且您必须将结构保存在调用方中)
  5. 完全消除此功能(根据您的操作,这可能不是一个好的解决方案)

【讨论】:

    【解决方案2】:

    char 数组存储在int2str 函数的堆栈帧中。这意味着当函数仍在运行时,堆栈上的内存是稳定且可用的。这就是您可以打印出字符串的原因。但是,一旦您从函数中返回,就无法保证内存会得到维护,并且可以像您所见证的那样被清除或重新使用。

    【讨论】:

    • 它可以被存储,但它不是必须的,因为 C 标准对堆栈一无所知。自动存储对象仅存储其范围无关紧要
    【解决方案3】:

    您正在返回对局部变量 char turnStr[10] 的引用。当函数退出时,该引用使用的内存被清理。所以在main() 中,你留下了一个悬空指针:char *s 指向不再有效的内存。

    【讨论】:

    • 没有。您可以返回自动存储对象。您应该(因为您当然可以)不将 reference 返回到此对象。 int foo(void){int a=5; return a;} 很好。 int *foo(void){int a=5; return &a;} 不是。
    • 没有回答问题“它能够打印出正确的字符串。我知道函数结束时堆栈空间无法返回,但我对添加 printf( "turnStr=%s\n",turnStr),它可以打印出字符串。"
    【解决方案4】:

    返回对本地对象的引用(指针)是一种未定义的行为。许多现代编译器发出警告并用 NULL 分配这个指针 - 例如 gcc。这段代码的另一个问题是另一个 UB。您的 char 数组不够长,无法容纳字符串

    如何整理(一个例子):

    char* int2str(int val);
    
    void main(){
     char *s = int2str(1001);
     printf("----s=%s\n",s);
    }
    
    char* int2str(int val){
      static char turnStr[20];
      sprintf(turnStr, "%d", val);
      //printf("turnStr=%s\n",turnStr);
      return turnStr;
    }
    

    https://godbolt.org/z/F3cx3E

    【讨论】:

      【解决方案5】:

      对于根据 C 标准的初学者,不带参数的函数 main 应声明为

      int main( void )
      

      也就是说它的返回类型应该是int

      您的程序具有未定义的行为,因为从函数 int2str 返回的指针指向具有自动存储持续时间的本地数组,该数组在退出函数后将不再存在。所以指针会有一个无效的值。本地数组占用的内存可以被任何其他函数的调用覆盖(例如在 main 中调用printf)。

      所以你必须为目标字符串动态分配内存。在函数内使用具有静态存储持续时间的本地数组并不是一个好主意,因为多次调用该函数会导致之前的结果字符串被覆盖。

      注意,例如INT_MIN的值(函数的用户可以传递任何有效的整数值)可以等于-2147483648,这需要12元素的字符数组来存储字符串代表这样一个数字。

      要计算所需的字符串大小,您可以调用 C 函数 snprintf,第二个参数等于 0

      这是一个演示程序。

      #include <stdio.h>
      #include <stdlib.h>
      
      char * int2str( int x )
      {
          int n = snprintf( NULL, 0, "%d", x );
      
          char *s = malloc( n + 1 );
      
          if ( s )
          {
              snprintf( s, n + 1, "%d", x );
          }
      
          return s;
      }
      
      int main(void) 
      {
          char *s = int2str( 1001 );
      
          if ( s ) puts( s );
      
          free( s );
      
          return 0;
      }
      

      它的输出是

      1001
      

      【讨论】:

      • 这是一种找到存储整数所需字节的聪明方法,但它不可移植。请参阅snprintf() 的注释,“函数 snprintf() 和 vsnprintf() 的 glibc 实现符合 C99 标准,即,从 glibc 版本 2.1 开始,其行为如上所述。直到 glibc 2.0.6,它们将返回-1 当输出被截断时。"
      • "...根据 C 标准,不带参数的函数 main 应声明为..." 这是根据执行环境的 托管环境 子章节。允许 main() 的实现定义形式,例如,所有独立系统都使用 main() 的实现定义形式,其中void main (void) 是最常见的。见this