문자열 리터럴 : 어디로 가나 요?

문자열 리터럴이 할당 / 저장되는 위치에 관심이 있습니다.

나는 하나의 흥미로운 답을 찾았어요 여기에서 말하는 :

문자열 인라인을 정의하면 실제로 프로그램 자체에 데이터가 포함되며 변경할 수 없습니다 (일부 컴파일러는 스마트 트릭으로 허용하지만 귀찮게하지는 않습니다).

그러나 C ++과 관련이 있으며 귀찮게하지 말라고 언급하지 않았습니다.

귀찮게 해요. = D

그래서 내 질문은 문자열 리터럴이 어디에 어떻게 어떻게 유지됩니까? 왜 변경하지 않아야합니까? 플랫폼에 따라 구현이 달라 집니까? “스마트 트릭”에 대해 자세히 설명하고 싶은 사람이 있습니까?



답변

일반적인 기술은 문자열 리터럴을 “읽기 전용 데이터”섹션에 배치하는 것입니다.이 섹션은 프로세스 공간에 읽기 전용으로 매핑되므로 변경할 수 없습니다.

플랫폼에 따라 다릅니다. 예를 들어, 단순한 칩 아키텍처는 읽기 전용 메모리 세그먼트를 지원하지 않으므로 데이터 세그먼트를 쓸 수 있습니다.

대신 문자열 리터럴을 변경 가능하게 만드는 트릭을 파악하십시오 (플랫폼에 크게 의존하고 시간이 지남에 따라 변경 될 수 있음). 배열을 사용하십시오.

char foo[] = "...";

컴파일러는 리터럴에서 배열이 초기화되도록 배열하고 배열을 수정할 수 있습니다.


답변

이에 대한 답은 없습니다. C 및 C ++ 표준에서는 문자열 리터럴에 고정 저장 기간이 있으며이를 수정하려고하면 정의되지 않은 동작이 발생하고 동일한 내용을 가진 여러 문자열 리터럴이 동일한 스토리지를 공유하거나 공유하지 않을 수 있다고합니다.

작성하는 시스템과 사용하는 실행 파일 형식의 기능에 따라 텍스트 코드에 프로그램 코드와 함께 저장되거나 초기화 된 데이터에 대한 별도의 세그먼트가있을 수 있습니다.

세부 사항을 결정하는 것은 플랫폼에 따라 다를 수 있습니다. 대부분은 플랫폼의 위치를 ​​알려주는 도구를 포함합니다. 일부는 원하는 경우 세부 정보를 제어 할 수도 있습니다 (예 : gnu ld를 사용하면 데이터, 코드 등을 그룹화하는 방법에 대한 스크립트를 제공 할 수 있습니다).


답변

왜 변경하지 않아야합니까?

정의되지 않은 동작이기 때문입니다. C99 N1256 draft 6.7.8 / 32 “Initialization” 에서 인용 :

예 8 : 선언

char s[] = "abc", t[3] = "abc";

“일반”문자 배열 객체를 정의 s하고t 요소가 문자열 리터럴로 초기화됩니다.

이 선언은

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

배열의 내용을 수정할 수 있습니다. 한편, 선언

char *p = "abc";

p“pointer to char”유형으로 정의 하고 길이가 4 인 “array of char”유형의 객체를 가리 키도록 초기화합니다. 길이가 4 인 요소는 문자열 리터럴로 초기화됩니다. p배열의 내용을 수정하는 데 사용하려고 하면 동작이 정의되지 않습니다.

그들은 어디로 갑니까?

GCC 4.8 x86-64 ELF 우분투 14.04 :

  • char s[]: 스택
  • char *s:
    • .rodata 객체 파일의 섹션
    • .text읽기 및 실행 권한은 있지만 쓰기 권한은없는 오브젝트 파일 섹션이 덤프 되는 동일한 세그먼트

프로그램:

#include <stdio.h>

int main() {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

컴파일 및 디 컴파일 :

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

출력 내용 :

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00
        c: R_X86_64_32S .rodata

따라서 문자열은 .rodata 섹션에 .

그때:

readelf -l a.out

포함 (간체) :

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000704 0x0000000000000704  R E    200000

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

기본 링커 스크립트는 모두 덤프이 수단 .text.rodata실행하지만 수정할 수는 없습니다 세그먼트에 ( Flags = R E). 이러한 세그먼트를 수정하려고하면 Linux에서 segfault가 발생합니다.

우리가 같은 일을한다면 char[]:

 char s[] = "abc";

우리는 얻는다 :

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

스택에 저장되고 (에 상대적으로 %rbp) 물론 수정할 수 있습니다.


답변

참고로 다른 답변을 백업하십시오.

표준 : ISO / IEC 14882 : 2003의 말 :

2.13. 문자열 리터럴

  1. […] 일반 문자열 리터럴은 “array of n const char” 유형 과 정적 저장 기간 (3.7)을 갖습니다.

  2. 모든 문자열 리터럴이 고유한지 (즉, 겹치지 않는 객체에 저장되는지) 구현 정의됩니다. 문자열 리터럴을 수정하려는 결과는 정의되지 않습니다.


답변

gcc는 .rodata주소 공간에서 “어딘가”에 매핑되고 읽기 전용으로 표시 되는 섹션을 만듭니다.

Visual C ++ ( cl.exe)는.rdata 같은 목적 섹션을 .

dumpbin또는 의 출력을 볼 수 있습니다objdump Linux 을보고 실행 파일의 섹션을 볼 수 있습니다.

예 :

>dumpbin vec1.exe
Microsoft (R) COFF/PE Dumper Version 8.00.50727.762
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file vec1.exe

File Type: EXECUTABLE IMAGE

  Summary

        4000 .data
        5000 .rdata  <-- here are strings and other read-only stuff.
       14000 .text


답변

실행 파일 형식 에 따라 다릅니다 . 이를 고려하는 한 가지 방법은 어셈블리 프로그래밍 인 경우 어셈블리 프로그램의 데이터 세그먼트에 문자열 리터럴을 넣을 수 있다는 것입니다. C 컴파일러는 이와 같은 작업을 수행하지만 바이너리가 컴파일되는 시스템에 따라 다릅니다.


답변

문자열 리터럴은 종종 읽기 전용 메모리에 할당되어 불변으로 만듭니다. 그러나 일부 컴파일러에서는 “스마트 트릭”으로 수정이 가능합니다. 스마트 트릭은 “메모리를 가리키는 문자 포인터 사용”입니다. 일부 컴파일러를 기억하면이를 허용하지 않을 수 있습니다.

char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"