动态分配的字符串文字的间接操作如何在 C 中真正起作用?

How does indirect manipulation of dynamically allocated string literals truly work in C?

就目前而言,我知道在 运行 时间内不能更改动态分配的字符串文字,否则您会遇到分段错误。

这是因为根据我在汇编代码中看到的,动态分配的字符串文字存储在 .rodata 段中,将这些文字放在我认为是只读内存的地方。

所以从理论上讲,这永远不会对我希望在每个现代 C 编译器中起作用的东西起作用:

#include <stdio.h>
#include <stdlib.h>

int 
main( void )
{
  char *p = ( char * )malloc( 10 * sizeof( char ) );
  if ( p == NULL )
  {
    puts( "Bad memory allocation error!" );
    return( EXIT_FAILURE );
  }
  else
  {
    p = "literal";
    printf( "%s\n", p );
    p[0] = 't'; // <-- This should cause a segmentation fault!
    printf( "%s\n", p ); // <-- This should never reach execution!
  }
  return( EXIT_SUCCESS );
}

但是,在研究了 tolower()toupper() 的工作原理之后。我发现很难理解这两个简单的功能如何能够完成我长期以来认为不可能的事情。这就是我的意思:

#include <stdio.h>

int
tolowercase( int c )
{
  return ( c >= 'A' && c <= 'Z' ) ? ( c + 32) : ( c );
}

int
retstrlen( char *str )
{
  int len = 0;
  while( *str != '[=11=]' ) { len++; str++; }
  return( len );
}

int
main( int argc, char **argv )
{
  for( int i = 0; i < argc; i++ )
  {
    for( int j = 0; j < retstrlen( argv[i] ); j++ )
    {
      argv[i][j] = tolowercase( argv[i][j] );
      printf( "%c", argv[i][j] );
    }
    printf( "\n" );
  }
  return 0;
}

我的自定义 tolower() 函数中定义的源代码如何不会像通常通过操作动态分配的字符串文字那样导致分段错误?

我唯一可以得出的假设是,由于 tolowercase() 有一个 int 参数和一个 return 类型的 int,因此编译器执行类型转换,间接操作 **argv .

我很确定我在这方面的方向是正确的,但我可能在这里弄错了我的整个术语,那么 **argv 到底发生了什么?

两点:

  1. p[0] = 't'; // <-- This should cause a segmentation fault! 不能保证,唯一可以保证的是调用 undefined behavior.

    对于字符串文字,来自 C11,章节 §6.4.5

    [...] If the program attempts to modify such an array, the behavior is undefined.

  2. 关于"How does the source code defined in my custom tolower() function not cause a segmentation fault as it normally would through manipulating dynamically allocated string literals?"

    引用 C11,章节 §5.1.2.2.1

    The parameters argc and argv and the strings pointed to by the argv array shall be modifiable by the program, and retain their last-stored values between program startup and program termination.

    所以,它们不是字符串文字,它们是完全可以修改的。

C 中没有动态分配的字符串文字

 p = "literal";

在代码的这一行中,您使用对字符串文字的引用覆盖了存储在指针中的值。 malloc 分配的内存丢失。然后您尝试修改字符串文字,这是一个 未定义的行为

您需要复制它

strcpy(p, "literal");

I know that dynamically allocated string literals cannot be changed during run-time, [...]

您一开始就错误地认为,一旦纠正,您的 问题的其余部分就变得无关紧要了。没有"dynamically allocated string literals"这样的想法,它是矛盾

当你调用malloc并将它的return值赋给p时,那么p指向堆上的一块内存:

char* p = malloc(10) ;

           Heap                      .rodata
         +-------------+             +------------+
         |             |             |            |
         |             |             |            |
         |             |             |            |
         +-------------+             |            |
p +----->+ Alloc block |             |            |
         +-------------+             |            |
         |             |             |            |
         |             |             |            |
         |             |             |            |
         |             |             |"literal"   |
         |             |             |            |
         +-------------+             +------------+

当您 重新分配 p 到文字字符串时,您将其更改为指向 .rodata 段中的字符串。它不再指向堆,并且您丢失了对该块的任何引用并导致内存泄漏; alloc 块 不能再释放回堆

p = "literal"

            Heap                      .rodata
         +-------------+             +------------+
         |             |             |            |
         |             |             |            |
         |             |             |            |
         +-------------+             |            |
 p +-+   | Alloc block |             |            |
     |   +-------------+             |            |
     |   |             |             |            |
     |   |             |             |            |
     |   |             |             |            |
     |   |             |       +---->+"literal"   |
     |   |             |       |     |            |
     |   +-------------+       |     +------------+
     |                         |
     |                         |
     +-------------------------+

此外,调用 free(p)(您在任何情况下都忽略了此操作)将失败,因为 p 不再是指向动态分配块的指针。

您应该做的是 复制 字符串文字到动态分配的内存中:

char *p = malloc( MAX_STR_LEN + 1 ) ;
strncpy( p, "literal", MAX_STR_LEN ) ;

那么内存是这样的:

                     Heap                      .rodata
          +-------------+             +------------+
          |             |             |            |
          |             |             |            |
          |             |             |            |
          +-------------+   strncpy() |            |
p +------>+ "literal"   +<---------+  |            |
          +-------------+          |  |            |
          |             |          |  |            |
          |             |          |  |            |
          |             |          |  |            |
          |             |          +--+"literal"   |
          |             |             |            |
          +-------------+             +------------+

现在p指向一个copy的文字串,但是没有no-longer一个文字串,而是_variable_data,并且可修改.

关键p 没有改变,只有p指向的数据改变了。您已经保持了对 alloc 块的控制,并且可以使用 `free(p) 将其释放回堆中。

谢谢大家帮助我理解我哪里出错了,让我修正示例,使它们最终正确!

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int
main( void )
{
  const int STR_MAX_LEN = 10;
  char *p = malloc( sizeof *p * STR_MAX_LEN );
  if ( p == NULL )
  {
    puts( "Bad memory allocation error!" );
    return EXIT_FAILURE;
  }
  else
  {
    strncpy( p, "literal", STR_MAX_LEN );
    printf( "%s", p );
    strncpy( p, "test", STR_MAX_LEN );
    printf( "%s", p);
    free( p );
    p = NULL;
  }
  return EXIT_SUCCESS;
}
#include <stdio.h>
#include <ctype.h>

char
*strlower( char *str )
{
  char *temp = str;
  while ( *temp )
  {
    *temp = tolower( ( unsigned char )*temp );
    temp++;
  }
  return str;
}

int
main( int argc, char **argv )
{
  for( int i = 0; i < argc; i++ )
  {
    strlower( argv[i] );
    printf( "%s\n", argv[i] );
  }
  return 0;
}

如果我的回答还有其他需要考虑的地方,请告诉我,感谢大家的宝贵建议和有关 C 语言的课程!