Как косвенные манипуляции с динамически размещаемыми строковыми литералами действительно работают в C? - PullRequest
0 голосов
/ 26 мая 2019

Насколько я знаю, я знаю, что динамически размещенные строковые литералы не могут быть изменены во время выполнения, иначе вы столкнетесь с ошибкой сегментации.

Это связано с тем, что динамически размещенные строковые литералы хранятся, как я видел в коде сборки, в сегменте .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 ]

2 голосов
/ 26 мая 2019

Два очка:

  1. p[0] = 't'; // <-- This should cause a segmentation fault! не гарантируется, единственное, что гарантировано, это вызвать неопределенное поведение .

    Длястроковые литералы, из C11, глава §6.4.5

    [...] Если программа пытается изменить такой массив, поведение не определено.

  2. Относительно "Как исходный код, определенный в моей пользовательской функции tolower (), не вызывает ошибку сегментации, как это обычно происходит при манипулировании динамически размещаемыми строковыми литералами?"

    Цитирование C11, глава §5.1.2.2.1

    Параметры argc и argv и строки, на которые указывает массив argv , должны изменяться с помощьюпрограмма, и сохранит свои последние сохраненные значения между запуском программы и завершением программы.

    Таким образом, они не являются строковыми литералами, они полностью модифицируемы.

1 голос
/ 26 мая 2019

Я знаю, что динамически распределяемые строковые литералы не могут быть изменены во время выполнения, [...]

Вы начинаете с неправильного представления, что при исправлении остальная часть вашего длинный вопрос не имеет значения.Не существует такого понятия, как «динамически размещаемые строковые литералы» , это оксюморон .

Когда вы вызываете 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).

0 голосов
/ 26 мая 2019

Спасибо всем за то, что помогли мне понять, где я ошибся, позвольте мне привести примеры в порядок, чтобы они наконец были правильными!

#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!

0 голосов
/ 26 мая 2019

В С. нет динамически размещаемых строковых литералов .

 p = "literal";

В этой строке кода вы перезаписываете значение, сохраненное в указателе, ссылкой на строковый литерал.Память, выделенная malloc, потеряна.Затем вы пытаетесь изменить строковый литерал, и это Неопределенное поведение .

Вам нужно скопировать его вместо

strcpy(p, "literal");
...