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


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 1힘 grep도 경기 (그래서 당신은 섹션 이름을 볼 수) 이전에 한 줄을 인쇄하고 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.