다음 코드는 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로 테스트되었습니다.
다음 코드는 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
수정할 수 있습니다.
다른 곳에서는 :
그래서 당신이 쓸 때 :
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
"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
char *str = "string";
컴파일러가 실행 파일의 수정 불가능한 부분에 넣는 문자열 리터럴에 대한 포인터를 할당합니다.
char str[] = "string";
수정 가능한 로컬 배열을 할당하고 초기화합니다.
int *b = {1,2,3)
것처럼 쓸 수 char *s = "HelloWorld"
있습니까?
@matli가 링크 한 C FAQ는 아직 언급하지 않았으므로 명확하게 설명합니다. 문자 배열을 초기화하는 것 이외의 다른 곳에서 문자열 리터럴 (소스에서 큰 따옴표로 묶인 문자열)을 사용 하는 경우 (예 : @ Mark의 두 번째 예 (정확하게 작동)는이 문자열이 컴파일러에 의해 특수 정적 문자열 테이블 에 저장되는데 , 이는 기본적으로 익명 인 (변수는 "name"이없는 전역 정적 변수 (물론 읽기 전용)를 만드는 것과 유사합니다. "). 읽기 전용 부분은 중요한 부분이며, 왜 @ 마크의 첫 번째 코드 예제 세그먼테이션 폴트 (segfault)이다.
int *b = {1,2,3)
것처럼 쓸 수 있을까요char *s = "HelloWorld"
있습니까?
그만큼
char *str = "string";
line은 포인터를 정의하고 리터럴 문자열을 가리 킵니다. 리터럴 문자열은 쓸 수 없으므로 다음을 수행하십시오.
str[0] = 'z';
세그 결함이 발생합니다. 일부 플랫폼에서는 리터럴이 쓰기 가능한 메모리에있을 수 있으므로 segfault가 표시되지 않지만 상관없이 유효하지 않은 코드 (정의되지 않은 동작이 발생 함)입니다.
라인 :
char str[] = "string";
문자 배열을 할당 하고 리터럴 문자열을 해당 배열에 복사 하여 완전히 쓸 수 있으므로 후속 업데이트는 문제가되지 않습니다.
int *b = {1,2,3)
것처럼 쓸 수 char *s = "HelloWorld"
있습니까?
// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";
// create an array of characters like this
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];
// now we try to change a character in the array first, this will work
*arr_p = 'E';
// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.
/*-----------------------------------------------------------------------------
* String constants can't be modified. A segmentation fault is the result,
* because most operating systems will not allow a write
* operation on read only memory.
*-----------------------------------------------------------------------------*/
//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array.
우선, str
가리키는 포인터입니다"string"
. 컴파일러는 쓸 수는 없지만 읽을 수있는 메모리의 위치에 문자열 리터럴을 넣을 수 있습니다. (실제로 a const char *
를 할당하기 때문에 경고가 발생했을 것입니다.char *
입니다. 경고가 비활성화되었거나 방금 무시 했습니까?)
두 번째로, 전체 액세스 권한을 가진 메모리 인 배열을 만들고를 사용하여 초기화합니다 "string"
. 당신은 char[7]
(문자 6, 종료 '\ 0'에 대한 6)을 만들고 있으며 원하는대로 무엇이든 할 수 있습니다.
const
접두사 변수를 읽기 전용으로 설정
char [N]
not 형식 const char [N]
이 있으므로 경고가 없습니다. (적어도 gcc에서이를 변경할 수 있습니다 -Wwrite-strings
.)
문자열이
char a[] = "string literal copied to stack";
char *p = "string literal referenced by p";
첫 번째 경우, 리터럴은 'a'가 범위 안에 들어 오면 복사됩니다. 여기서 'a'는 스택에 정의 된 배열입니다. 이는 문자열이 스택에 생성되고 해당 데이터가 일반적으로 읽기 전용 인 코드 (텍스트) 메모리에서 복사됨을 의미합니다 (구현 특정, 컴파일러는이 읽기 전용 프로그램 데이터를 읽기 / 쓰기 가능 메모리에도 배치 할 수 있음) ).
두 번째 경우, p는 스택 (로컬 범위)에 정의되고 다른 곳에 저장된 문자열 리터럴 (프로그램 데이터 또는 텍스트)을 참조하는 포인터입니다. 일반적으로 그러한 메모리를 수정하는 것은 좋은 습관이 아니며 권장되지 않습니다.
액세스 할 수없는 메모리에 액세스하려고하면 분할 오류가 발생합니다.
char *str
수정 불가능한 문자열에 대한 포인터입니다 (segfault를 얻는 이유).
반면 char str[]
배열이며 수정할 수 있습니다 ..