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

时间:2019-05-26 06:33:52

标签: c memory dynamic allocation tolower

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

这是由于以下事实:根据我在汇编代码中看到的内容,动态分配的字符串文字存储在.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 != '\0' ) { 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,返回类型为int,因此编译器执行类型转换,从而间接操纵** argv。

我很确定自己对此事的正确方向,但是我可能在这里误解了整个术语,所以** argv到底发生了什么?

4 个答案:

答案 0 :(得分:2)

两点:

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

    对于字符串文字,请参见C11,第6.4.5章

      

    [...]如果程序尝试修改这样的数组,则行为是   未定义。

  2. 关于“在我的自定义tolower()函数中定义的源代码如何不会像通常通过处理动态分配的字符串文字那样引起分段错误?”

    引用C11,第§5.1.2.2.1

      

    参数argcargv以及argv数组指向的字符串   可由程序修改,并在程序之间保留其最后存储的值   启动和程序终止。

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

答案 1 :(得分:1)

  

我知道动态分配的字符串文字不能在运行时更改,[...]

您开始时有一个误解,即纠正后会使您剩下的 long 问题变得无关紧要。没有“动态分配的字符串文字” 这样的想法,它是 oxymoron

调用malloc并将其返回值分配给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不再是指向动态分配的块的指针。

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

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

然后,内存如下所示:

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

现在p指向文字字符串的副本,但不再不再是文字字符串,而是_variable_data,并且是可修改的

至关重要的p 未更改,只有p指向的数据已更改。您已经保持了对alloc块的控制,可以使用`free(p)将其释放回堆中。

答案 2 :(得分:0)

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

 p = "literal";

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

您需要复制它

strcpy(p, "literal");

答案 3 :(得分:0)

谢谢大家帮助我理解我出了问题的地方,让我修正示例,使它们最终正确!

#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语言的课程!