다음 코드는 2 행에서 세그먼트 오류를 수신합니다.
char *str = "string";
str[0] = 'z'; // could be also written as *str = 'z'
printf("%s\n", str);
이것이 완벽하게 작동하는 동안 :
char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
MSVC 및 GCC로 테스트되었습니다.
답변
C FAQ, 질문 1.32 참조
Q : 이러한 초기화의 차이점은 무엇입니까?
char a[] = "string literal";
char *p = "string literal";
에 새 값을 할당하려고하면 프로그램이 중단됩니다p[i]
.A : 문자열 리터럴 (C 소스에서 큰 따옴표로 묶인 문자열의 공식 용어)은 두 가지 약간 다른 방식으로 사용될 수 있습니다.
- 의 선언에서와 같이 char 배열의 초기화
char a[]
자로 해당 배열에있는 문자의 초기 값 (필요한 경우 크기)을 지정합니다.- 다른 곳에서는 명명되지 않은 정적 문자 배열로 바뀌며이 명명되지 않은 배열은 읽기 전용 메모리에 저장 될 수 있으므로 반드시 수정할 수는 없습니다. 표현식 컨텍스트에서, 배열은 평소와 같이 한 번에 포인터로 변환되므로 (섹션 6 참조) 두 번째 선언은 p를 초기화하여 명명되지 않은 배열의 첫 번째 요소를 가리 키도록합니다.
일부 컴파일러에는 문자열 리터럴을 쓸 수 있는지 여부를 제어하는 스위치가 있으며 (오래된 코드를 컴파일하기 위해) 문자열 리터럴을 형식적으로 const char 배열로 처리하도록하는 옵션이있을 수 있습니다 (더 나은 오류 포착).
답변
일반적으로 문자열 리터럴은 프로그램이 실행될 때 읽기 전용 메모리에 저장됩니다. 이것은 실수로 문자열 상수를 변경하지 못하게하기위한 것입니다. 첫 번째 예에서는 "string"
읽기 전용 메모리에 저장되며 *str
첫 번째 문자를 가리 킵니다. segfault는 첫 번째 문자를 다음과 같이 변경하려고 할 때 발생합니다'z'
.
두 번째 예에서 문자열 "string"
은 컴파일러가 읽기 전용 홈에서 배열 로 복사 합니다 str[]
. 그런 다음 첫 번째 문자를 변경할 수 있습니다. 각 주소를 인쇄하여이를 확인할 수 있습니다.
printf("%p", str);
또한 str
두 번째 예제에서 크기를 인쇄하면 컴파일러가 7 바이트를 할당했음을 알 수 있습니다.
printf("%d", sizeof(str));
답변
이 답변의 대부분은 정확하지만 조금 더 명확하게 추가하기 위해 …
사람들이 말하는 “읽기 전용 메모리”는 ASM 용어의 텍스트 세그먼트입니다. 명령어가로드되는 메모리의 동일한 위치입니다. 보안과 같은 명백한 이유로 읽기 전용입니다. 문자열로 초기화 된 char *를 만들면 문자열 데이터가 텍스트 세그먼트로 컴파일되고 프로그램이 포인터를 초기화하여 텍스트 세그먼트를 가리 킵니다. 따라서 변경하려고하면 kaboom. Segfault.
배열로 쓰여질 때 컴파일러는 초기화 된 문자열 데이터를 데이터 세그먼트에 대신 배치합니다. 이는 전역 변수와 같은 곳에 있습니다. 데이터 세그먼트에 명령어가 없기 때문에이 메모리는 변경 가능합니다. 이번에는 컴파일러가 문자 배열 (여전히 char * 임)을 초기화 할 때 텍스트 세그먼트가 아닌 데이터 세그먼트를 가리키며 런타임에 안전하게 변경할 수 있습니다.
답변
문자열에 쓸 때 왜 세그먼테이션 오류가 발생합니까?
C99 N1256 초안
문자열 리터럴에는 두 가지 용도가 있습니다.
-
초기화
char[]
:char c[] = "abc";
이것은 “더 많은 마법”이며 6.7.8 / 14 “초기화”에 설명되어 있습니다.
문자 유형의 배열은 선택적으로 중괄호로 묶인 문자열 리터럴로 초기화 될 수 있습니다. 문자열 리터럴의 연속 문자 (공간이 있거나 배열의 크기를 알 수없는 경우 종료 널 문자 포함)는 배열의 요소를 초기화합니다.
따라서 이것은 바로 가기입니다.
char c[] = {'a', 'b', 'c', '\0'};
다른 일반 배열과 마찬가지로
c
수정할 수 있습니다. -
다른 곳에서는 :
- 이름이없는
- 문자 배열 C와 C ++에서 문자열 리터럴의 유형은 무엇입니까?
- 정적 스토리지
- 수정하면 UB를 제공합니다.
그래서 당신이 쓸 때 :
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
.
우리가 같은 일을한다면 char[]
:
char s[] = "abc";
우리는 얻는다 :
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
스택에 저장됩니다. %rbp
).
그러나 참고 기본 링커 스크립트 박았 .rodata
및 .text
실행이 동일한 세그먼트,하지만 쓰기 권한이있다. 이것은 다음과 같이 볼 수 있습니다.
readelf -l a.out
포함하는:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
답변
첫 번째 코드에서 “string”은 문자열 상수이며 문자열 상수는 종종 읽기 전용 메모리에 배치되므로 수정해서는 안됩니다. “str”은 상수를 수정하는 데 사용되는 포인터입니다.
두 번째 코드에서 “string”은 배열 이니셜 라이저입니다.
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
“str”은 스택에 할당 된 배열이며 자유롭게 수정할 수 있습니다.
답변
"whatever"
첫 번째 예제의 컨텍스트에서 유형 은 const char *
(const가 아닌 char *에 할당하더라도), 따라서 시도하거나 쓰지 않아야 함을 의미합니다.
컴파일러는 문자열을 메모리의 읽기 전용 부분에 배치하여이를 시행하므로 쓰기는 segfault를 생성합니다.
답변
이 오류 또는 문제를 이해하려면 먼저 포인터와 배열의 차이점을 알아야하므로 여기에서 먼저 차이점을 설명합니다.
문자열 배열
char strarray[] = "hello";
메모리 배열은 연속 메모리 셀에 저장되고 [h][e][l][l][o][\0] =>[]
1 문자 바이트 크기 메모리 셀로 저장 되며이 연속 메모리 셀은 strarray라는 이름으로 액세스 할 수 있습니다. 여기서 문자열 배열 strarray
자체는 초기화 된 문자열의 모든 문자를 포함합니다. 여기서는 "hello"
인덱스 값으로 각 문자에 액세스하여 메모리 내용을 쉽게 변경할 수 있습니다.
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
그것의 값은 'm'
이렇게 strarray 값으로 변경되었다 "mello"
;
여기서는 문자별로 문자를 변경하여 문자열 배열의 내용을 변경할 수 있지만 다른 문자열을 직접 초기화 할 수는 없습니다. strarray="new string"
입니다.
바늘
우리 모두가 포인터가 메모리의 메모리 위치를 가리키는 것을 알고 있으므로 초기화되지 않은 포인터는 임의의 메모리 위치를 가리키며 초기화 후에 특정 메모리 위치를 가리 킵니다.
char *ptr = "hello";
여기서 포인터 ptr은 "hello"
읽기 전용 메모리 (ROM)에 저장된 상수 문자열 인 문자열로 초기화 되므로 "hello"
ROM에 저장된대로 변경할 수 없습니다
ptr은 스택 섹션에 저장되며 상수 문자열을 가리 킵니다. "hello"
따라서 읽기 전용 메모리에 액세스 할 수 없으므로 ptr [0] = ‘m’은 유효하지 않습니다
그러나 ptr은 포인터 일 뿐이므로 다른 문자열 값으로 직접 초기화 할 수 있으므로 데이터 유형의 변수의 모든 메모리 주소를 가리킬 수 있습니다
ptr="new string"; is valid
