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


161

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

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

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

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

귀찮게 해요. = D

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

답변:


125

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

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

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

char foo[] = "...";

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


5
예, 가변 문자열을 원할 때 배열을 사용합니다. 나는 단지 궁금했다. 감사.
Chris Cooper

2
변경 가능한 문자열에 배열을 사용할 때 버퍼 오버플로에주의해야합니다. 단순히 배열 길이보다 긴 문자열을 작성하면 (예 : foo = "hello"이 경우) 의도하지 않은 부작용이 발생할 수 있습니다 ... new또는 무언가를 가진 메모리 할당 )
johnny

2
배열 문자열을 사용할 때 스택이나 다른 곳으로 이동합니까?
Suraj Jain

char *p = "abc";@ChrisCooper가 다르게 말한 것처럼 변경 가능한 문자열을 만드는 데 사용할 수 없습니다
KPMG

52

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

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

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


1
문자열 데이터가 .text 세그먼트에 직접 저장되지는 ​​않습니다. 정말 짧은 리터럴, 나는 같은 컴파일러 생성 코드를 볼 수있는 movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)문자열에 대한 "AB",하지만 대부분의 시간, 그것은 같은 비 코드 세그먼트에있을 것입니다 .data이나 .rodata또는 여부 대상 지원에 따라 (같은 읽기 전용 세그먼트).
Adam Rosenfield

문자열 리터럴이 프로그램의 전체 기간 동안 유효하다면 정적 객체가 파괴되는 동안에도 문자열 리터럴에 대한 const 참조를 반환하는 것이 유효합니까? 이 프로그램이 런타임 오류를 나타내는 이유 ideone.com/FTs1Ig
Destructor

@AdamRosenfield : 언젠가 지루한 경우 레거시 UNIX a.out 형식 (예 : freebsd.org/cgi/… ) 을보고 싶을 수 있습니다 . 빠르게 알아 차릴 수있는 한 가지는 항상 하나의 데이터 세그먼트 만 지원한다는 점입니다. 읽기 전용 문자열 리터럴을 원한다면, 본질적으로는 그들 만이 배치 할 수 있습니다 이동 텍스트 세그먼트이다 (그리고 예, 시간 링커에 자주 정확하게했다).
Jerry Coffin

48

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

정의되지 않은 동작이기 때문입니다. 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) 물론 수정할 수 있습니다.


22

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

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

2.13. 문자열 리터럴

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

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


2
유용한 정보이지만 공지 사항 링크는 C ++ 용이지만 질문은 c와 관련이 있습니다.
Grijesh Chauhan

1
2.13에서 2 위를 확인했다. -Os 옵션 (크기에 최적화)을 사용하면 gcc는 .rodata에서 문자열 리터럴을 겹칩니다.
Peng Zhang

14

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

1
objdump를 사용하여 rdata 섹션을 분해하는 방법을 볼 수 없습니다.
user2284570 10

@ user2284570은 해당 섹션에 어셈블리가 없기 때문입니다. 데이터가 포함되어 있습니다.
Alex Budovski

1
더 읽기 쉬운 출력을 얻는 것만으로도 충분합니다. 나는 그 섹션에 주소 대신 해체로 인라인 된 문자열을 얻고 싶다는 것을 의미합니다. (옷자락 당신은 알고 printf("some null terminated static string");대신 printf(*address);C에서)
user2284570

4

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


2

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

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

0

이것은 컴파일러마다 다를 수 있으므로 가장 좋은 방법은 검색된 문자열 리터럴에 대한 오브젝트 덤프를 필터링하는 것입니다.

objdump -s main.o | grep -B 1 str

여기서 -s힘은 objdump모든 섹션의 전체 내용을 표시하는 main.o오브젝트 파일입니다 -B 1grep도 경기 (그래서 당신은 섹션 이름을 볼 수) 이전에 한 줄을 인쇄하고 str대한 당신있는 거 검색 문자열 리터럴입니다.

Windows 시스템에서 gcc를 사용하고 다음 main과 같이 선언 된 하나의 변수

char *c = "whatever";

달리는

objdump -s main.o | grep -B 1 whatever

보고

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.