C에서는 다음과 같은 선언에 문자열 리터럴을 사용할 수 있습니다.
char s[] = "hello";
또는 이와 같이 :
char *s = "hello";
차이점은 무엇입니까? 컴파일 및 런타임 모두에서 저장 기간과 관련하여 실제로 어떤 일이 발생하는지 알고 싶습니다.
C에서는 다음과 같은 선언에 문자열 리터럴을 사용할 수 있습니다.
char s[] = "hello";
또는 이와 같이 :
char *s = "hello";
차이점은 무엇입니까? 컴파일 및 런타임 모두에서 저장 기간과 관련하여 실제로 어떤 일이 발생하는지 알고 싶습니다.
답변:
차이점은
char *s = "Hello world";
배치됩니다 "Hello world"
에서 메모리의 읽기 전용 부품 및 제작 s
하는이 메모리에있는 모든 쓰기 작업이 불법하게 포인터를.
하는 동안 :
char s[] = "Hello world";
리터럴 문자열을 읽기 전용 메모리에 넣고 문자열을 스택의 새로 할당 된 메모리에 복사합니다. 따라서 만들기
s[0] = 'J';
적법한.
"Hello world"
은 두 예제 모두에서 "메모리의 읽기 전용 부분"에 있습니다. 어레이와의 예는 포인트 어레이와 예가 복사 배열 요소에 문자.
char msg[] = "hello, world!";
문자열 만 포함 된 파일을 깨끗하게 컴파일 하면 초기화 된 데이터 섹션에 나타납니다. char * const
읽기 전용 데이터 섹션에서 끝나도록 선언 된 경우 gcc-4.5.3
먼저 함수 인수에서 정확히 동일합니다.
void foo(char *x);
void foo(char x[]); // exactly the same in all respects
다른 상황에서는 배열 char *
을 char []
할당하는 동안 포인터를 할당합니다. 전자의 경우에는 줄이 어디로 가나 요? 컴파일러는 문자열 리터럴을 보유하기 위해 정적 익명 배열을 비밀리에 할당합니다. 그래서:
char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;
이 포인터를 통해이 익명 배열의 내용을 수정하려고 시도해서는 안됩니다. 효과는 정의되어 있지 않습니다 (종종 충돌을 의미 함).
x[1] = 'O'; // BAD. DON'T DO THIS.
배열 구문을 사용하면이를 새로운 메모리에 직접 할당합니다. 따라서 수정은 안전합니다.
char x[] = "Foo";
x[1] = 'O'; // No problem.
그러나 배열은 포함 범위 내에서만 유효하므로 함수 에서이 작업을 수행하는 경우이 배열에 대한 포인터를 반환하거나 누출시키지 마십시오 strdup()
. 대신 또는 이와 유사한 사본을 만드십시오 . 물론 배열이 전역 범위에 할당 되어도 아무런 문제가 없습니다.
이 선언 :
char s[] = "hello";
값으로 초기화 된 크기 6 의 배열 인 객체 하나를 만듭니다 . 이 배열이 메모리에서 할당되는 위치와 수명은 선언이 나타나는 위치에 따라 다릅니다. 선언이 함수 내에 있으면 선언 된 블록의 끝까지 존재하며 거의 확실하게 스택에 할당됩니다. 이 함수 외부에 있다면, 그것은 것입니다 아마 프로그램이 실행될 때 쓰기 가능한 메모리에 실행 파일에서로드되는 "초기화 데이터 세그먼트"에 저장 될 수있다.char
s
'h', 'e', 'l', 'l', 'o', '\0'
반면에,이 선언은 :
char *s ="hello";
두 개의 객체를 만듭니다 .
char
의 값을 포함하는 (S) 'h', 'e', 'l', 'l', 'o', '\0'
에 이름이없고 가지며, 정적 저장 지속 기간 (이 프로그램의 전체 수명 사는 것을 의미 함); 과s
이름이없는 읽기 전용 배열에서 첫 번째 문자의 위치로 초기화되는 pointer-to-char 유형의 변수 .명명되지 않은 읽기 전용 배열은 일반적으로 프로그램의 "텍스트"세그먼트에 있습니다. 즉, 코드 자체와 함께 디스크에서 읽기 전용 메모리로로드됩니다. s
메모리 에서 포인터 변수 의 위치는 선언이 나타나는 위치에 따라 다릅니다 (첫 번째 예 에서처럼).
char s[] = "hello"
경우 "hello"
배열은 초기화 방법을 컴파일러에 알려주는 이니셜 라이저 일뿐입니다. 예를 들어 s
정적 저장 기간이있는 경우 텍스트 세그먼트에 해당 문자열이 생길 수도 있고 그렇지 않을 수도 있습니다. "hello"
객체 의 유일한 인스턴스 는 초기화 된 데이터 세그먼트 에 있을 가능성이 높습니다 s
. s
자동 저장 기간이 있더라도 복사본 (예 :)이 아닌 일련의 리터럴 저장소로 초기화 될 수 있습니다 movl $1819043176, -6(%ebp); movw $111, -2(%ebp)
.
char s[] = "Hello world";
에서 리터럴 문자열을 읽기 전용 메모리에 넣고 스택의 새로 할당 된 메모리에 문자열을 복사하도록 작성되었습니다. 그러나 답은 읽기 전용 메모리에 넣은 리터럴 문자열에 대해서만 말하고 문장의 두 번째 부분은 생략합니다 copies the string to newly allocated memory on the stack
. 그렇다면 두 번째 부분을 지정하지 않은 답변이 불완전합니까?
char s[] = "Hellow world";
은 초기화 프로그램 일 뿐이며 반드시 별도의 읽기 전용 사본으로 저장되지는 않습니다. 경우 s
다음 정적 저장 기간이있는 경우에만 문자열을 복사의 위치에 읽기 - 쓰기 세그먼트에있을 가능성이 s
, 심지어되지 다음 경우 컴파일러는로드 즉시 지시 또는 유사한로 배열을 초기화 할 선택이 아닌 복사 할 수 있습니다 읽기 전용 문자열에서. 요점은이 경우 이니셜 라이저 문자열 자체에는 런타임이 존재하지 않는다는 것입니다.
선언이 주어지면
char *s0 = "hello world";
char s1[] = "hello world";
다음과 같은 가상 메모리 맵을 가정하십시오.
0x01 0x02 0x03 0x04 0x00008000 : 'h' 'e' 'l' 'l' 0x00008004 : 'o' '' 'w' 'o' 0x00008008 : 'r' 'l' 'd'0x00 ... s0 : 0x00010000 : 0x00 0x00 0x80 0x00 s1 : 0x00010004 : 'h' 'e' 'l' 'l' 0x00010008 : 'o' '' 'w' 'o' 0x0001000C : 'r' 'l' 'd'0x00
문자열 리터럴 "hello world"
은 고정 저장 기간을 갖는 char
( const char
C ++의) 12 요소 배열로 , 프로그램 시작시 할당되고 프로그램이 종료 될 때까지 할당 된 메모리를 의미합니다. 문자열 리터럴의 내용을 수정하려고하면 정의되지 않은 동작이 호출됩니다.
라인
char *s0 = "hello world";
자동 저장 기간이 s0
있는 포인터로 정의 하고 char
(변수 s0
가 선언 된 범위에 대해서만 변수 가 존재 함을 의미 ) 문자열 리터럴 ( 이 예에서는) 의 주소를 여기 에 복사 0x00008000
합니다. 이후 있습니다 s0
문자열 리터럴에 포인트가 그것을 수정하려고하는 어떤 기능 (예를 들면, 인수로 사용할 수 없습니다 strtok()
, strcat()
, strcpy()
, 등).
라인
char s1[] = "hello world";
정의 s1
의 12 요소 어레이와 같은 char
자동 보관 기간 및 복사와 (길이 문자열 리터럴에서 가져온) 내용 어레이에 리터럴한다. 메모리 맵에서 알 수 있듯이 두 개의 문자열 사본이 있습니다 "hello world"
. 차이점은에 포함 된 문자열을 수정할 수 있다는 것입니다 s1
.
s0
그리고 s1
대부분의 상황에서 교환 할 수있다; 예외는 다음과 같습니다.
sizeof s0 == sizeof (char*)
sizeof s1 == 12
type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char
s0
다른 문자열 리터럴 또는 다른 변수 를 가리 키도록 변수 를 재 지정할 수 있습니다 . s1
다른 배열을 가리 키도록 변수 를 재 할당 할 수 없습니다 .
C99 N1256 초안
문자열 리터럴에는 두 가지 용도가 있습니다.
초기화 char[]
:
char c[] = "abc";
이것은 "더 많은 마법"이며 6.7.8 / 14 "초기화"에 설명되어 있습니다.
문자 유형의 배열은 선택적으로 중괄호로 묶인 문자열 리터럴로 초기화 될 수 있습니다. 문자열 리터럴의 연속 문자 (공간이 있거나 배열의 크기를 알 수없는 경우 종료 널 문자 포함)는 배열의 요소를 초기화합니다.
따라서 이것은 바로 가기입니다.
char c[] = {'a', 'b', 'c', '\0'};
다른 일반 배열과 마찬가지로 c
수정할 수 있습니다.
다른 곳에서는 :
그래서 당신이 쓸 때 :
char *c = "abc";
이것은 다음과 유사합니다.
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
에서 암시 적 캐스트 주 char[]
에 char *
항상 합법적이다.
그런 다음 수정 c[0]
하면 __unnamed
UB 도 수정 됩니다.
이것은 6.4.5 "문자열 리터럴"에 문서화되어 있습니다 :
5 변환 단계 7에서는 문자열 리터럴 또는 리터럴에서 생성 된 각 멀티 바이트 문자 시퀀스에 값이 0 인 바이트 또는 코드가 추가됩니다. 그런 다음 멀티 바이트 문자 시퀀스는 시퀀스를 포함하기에 충분한 정적 저장 기간 및 길이의 배열을 초기화하는 데 사용됩니다. 문자열 리터럴의 경우 배열 요소에는 char 유형이 있으며 멀티 바이트 문자 시퀀스의 개별 바이트로 초기화됩니다 [...]
6 해당 배열에 적절한 값이있는 경우 이러한 배열이 고유한지 여부는 지정되지 않았습니다. 프로그램이 이러한 배열을 수정하려고하면 동작이 정의되지 않습니다.
6.7.8 / 32 "초기화"는 직접적인 예를 제공합니다.
예 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"유형의 객체를 가리 키도록 초기화합니다.p
배열의 내용을 수정하는 데 사용하려고 하면 동작이 정의되지 않습니다.
GCC 4.8 x86-64 ELF 구현
프로그램:
#include <stdio.h>
int main(void) {
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
결론 : GCC는 char*
이를 .rodata
섹션이 아닌 섹션에 저장 합니다 .text
.
그러나 참고 기본 링커 스크립트 풋 .rodata
과 .text
같은에서 세그먼트 실행할 수있다,하지만 쓰기 권한. 이것은 다음과 같이 볼 수 있습니다.
readelf -l a.out
포함하는:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
우리가 같은 일을한다면 char[]
:
char s[] = "abc";
우리는 얻는다 :
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
스택에 저장됩니다 (에 상대적 %rbp
).
char s[] = "hello";
이니셜 라이저 (5 + 1 s) 를 보유하기에 충분히 긴 s
배열 임을 선언 하고 주어진 문자열 리터럴의 멤버를 배열에 복사하여 배열을 초기화합니다.char
char
char *s = "hello";
는 s
하나 이상의 (이 경우에는 더 많은)에 대한 포인터로 선언 char
하고 리터럴을 포함하는 고정 된 (읽기 전용) 위치를 직접 가리 킵니다 "hello"
.
s
에 대한 포인터 const char
입니다.
char s[] = "Hello world";
여기에 s
원하는 문자를 덮어 쓸 수있는 문자 배열이 있습니다.
char *s = "hello";
문자열 리터럴은이 포인터 s
가 가리키는 메모리 어딘가에 이러한 문자 블록을 작성하는 데 사용 됩니다. 여기서는 객체를 변경하여 가리키는 객체를 재 할당 할 수 있지만, 문자열 리터럴을 가리키는 한은 변경할 수없는 문자 블록을 가리 킵니다.
또한 읽기 전용 목적으로 두 가지 사용이 동일하므로 다음과 같이 []
또는 *(<var> + <index>)
형식으로 색인을 생성하여 char에 액세스 할 수 있습니다 .
printf("%c", x[1]); //Prints r
과:
printf("%c", *(x + 1)); //Prints r
분명히, 당신이하려고하면
*(x + 1) = 'a';
읽기 전용 메모리에 액세스하려고 할 때 세그먼트 오류가 발생했을 수 있습니다.
x[1] = 'a';
물론 플랫폼에 따라 segfault와 다를 바 없습니다 .
char *str = "Hello";
위의 설정은 메모리의 읽기 전용으로 플래그가 지정된 프로그램의 이진 이미지에 하드 코딩 된 리터럴 값 "Hello"를 가리 키도록 str을 설정합니다.이 문자열 리터럴의 모든 변경은 불법이며 세그먼트 오류를 발생시킵니다.
char str[] = "Hello";
스택에서 새로 할당 된 메모리에 문자열을 복사합니다. 따라서 변경하는 것은 허용되며 합법적입니다.
means str[0] = 'M';
str을 "Mello"로 변경합니다.
자세한 내용은 비슷한 질문을 통해 진행하십시오.
"char * s"로 초기화되었지만 "char s []"가 아닌 문자열에 쓸 때 세그먼테이션 오류가 발생하는 이유는 무엇입니까?
주석에 비추어 볼 때 다음과 같은 점이 분명해야합니다. char * s = "hello"; 나쁜 생각이며 매우 좁은 범위에서 사용해야합니다.
이것은 "정확도"가 "좋은 것"임을 지적하는 좋은 기회 일 수 있습니다. 언제 어디서나 "const"키워드를 사용하여 "릴렉스 된"호출자 또는 프로그래머로부터 포인터를 사용할 때 가장 "릴렉스 된"코드를 보호하십시오.
충분한 멜로 드라마, 여기에 "const"로 포인터를 장식 할 때 달성 할 수있는 것이 있습니다. (참고 : 포인터 선언을 오른쪽에서 왼쪽으로 읽어야합니다.) 다음은 포인터를 사용할 때 자신을 보호하는 3 가지 방법입니다.
const DBJ* p means "p points to a DBJ that is const"
즉, p를 통해 DBJ 객체를 변경할 수 없습니다.
DBJ* const p means "p is a const pointer to a DBJ"
즉, p를 통해 DBJ 객체를 변경할 수 있지만 포인터 p 자체는 변경할 수 없습니다.
const DBJ* const p means "p is a const pointer to a const DBJ"
즉, 포인터 p 자체를 변경하거나 p를 통해 DBJ 객체를 변경할 수 없습니다.
시도 된 const-ant 돌연변이와 관련된 오류는 컴파일 타임에 잡 힙니다. const에는 런타임 공간이나 속도 패널티가 없습니다.
(물론 C ++ 컴파일러를 사용하고 있습니까?)
--DBJ