char s []와 char * s의 차이점은 무엇입니까?


506

C에서는 다음과 같은 선언에 문자열 리터럴을 사용할 수 있습니다.

char s[] = "hello";

또는 이와 같이 :

char *s = "hello";

차이점은 무엇입니까? 컴파일 및 런타임 모두에서 저장 기간과 관련하여 실제로 어떤 일이 발생하는지 알고 싶습니다.



8
char * s = "hello", 여기서는 런타임에 다른 문자열을 가리킬 수 있습니다. 상수 포인터가 아니라는 것을 의미합니다. 런타임에 다른 값을 지정할 수 있습니다 p = "Nishant", s [] s는 상수 포인터입니다. .. 다른 문자열을 재 지정할 수는 없지만 s [index]에 다른 문자 값을 할당 할 수 있습니다.
Nishant Kumar

답변:


541

차이점은

char *s = "Hello world";

배치됩니다 "Hello world"에서 메모리의 읽기 전용 부품 및 제작 s하는이 메모리에있는 모든 쓰기 작업이 불법하게 포인터를.

하는 동안 :

char s[] = "Hello world";

리터럴 문자열을 읽기 전용 메모리에 넣고 문자열을 스택의 새로 할당 된 메모리에 복사합니다. 따라서 만들기

s[0] = 'J';

적법한.


22
리터럴 문자열 "Hello world"은 두 예제 모두에서 "메모리의 읽기 전용 부분"에 있습니다. 어레이와의 예는 포인트 어레이와 예가 복사 배열 요소에 문자.
pmg

28
pmg : 두 번째 경우, 리터럴 문자열은 반드시 하나의 연속 된 객체로서 메모리에 존재하지 않아도됩니다. 단지 초기 자일뿐입니다. 그들.
caf

10
char 배열 예제는 반드시 문자열을 스택에 배치 할 필요 는 없습니다 . 파일 레벨에 표시되면 초기화 된 데이터 세그먼트 일 것입니다.
caf

9
나는 문자의 = "xx는"하지 않는 것을 지적하고 싶습니다 있습니다 (일부 구현 예를 들어, 어떤 MMU를이 없습니다) 읽기 전용 메모리에있을 수 있습니다. n1362 c1x 초안은 단순히 그러한 배열을 수정하면 정의되지 않은 동작이 발생한다는 것을 나타냅니다. 그러나 어쨌든 +1, 그 행동에 의존하는 것은 어리석은 일입니다.
paxdiablo

3
char msg[] = "hello, world!"; 문자열 만 포함 된 파일을 깨끗하게 컴파일 하면 초기화 된 데이터 섹션에 나타납니다. char * const읽기 전용 데이터 섹션에서 끝나도록 선언 된 경우 gcc-4.5.3
gcbenison

152

먼저 함수 인수에서 정확히 동일합니다.

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(). 대신 또는 이와 유사한 사본을 만드십시오 . 물론 배열이 전역 범위에 할당 되어도 아무런 문제가 없습니다.


72

이 선언 :

char s[] = "hello";

값으로 초기화 된 크기 6 의 배열 인 객체 하나를 만듭니다 . 이 배열이 메모리에서 할당되는 위치와 수명은 선언이 나타나는 위치에 따라 다릅니다. 선언이 함수 내에 있으면 선언 된 블록의 끝까지 존재하며 거의 확실하게 스택에 할당됩니다. 이 함수 외부에 있다면, 그것은 것입니다 아마 프로그램이 실행될 때 쓰기 가능한 메모리에 실행 파일에서로드되는 "초기화 데이터 세그먼트"에 저장 될 수있다.chars'h', 'e', 'l', 'l', 'o', '\0'

반면에,이 선언은 :

char *s ="hello";

두 개의 객체를 만듭니다 .

  • 읽기 전용 (6 개)의 배열 char의 값을 포함하는 (S) 'h', 'e', 'l', 'l', 'o', '\0'에 이름이없고 가지며, 정적 저장 지속 기간 (이 프로그램의 전체 수명 사는 것을 의미 함); 과
  • s이름이없는 읽기 전용 배열에서 첫 번째 문자의 위치로 초기화되는 pointer-to-char 유형의 변수 .

명명되지 않은 읽기 전용 배열은 일반적으로 프로그램의 "텍스트"세그먼트에 있습니다. 즉, 코드 자체와 함께 디스크에서 읽기 전용 메모리로로드됩니다. s메모리 에서 포인터 변수 의 위치는 선언이 나타나는 위치에 따라 다릅니다 (첫 번째 예 에서처럼).


1
"hello"메모리에 대한 두 선언 모두 comiple time?에 할당됩니다. 또 다른 것은 char * p = "hello"여기에 "hello"는 답변에 명시된대로 텍스트 세그먼트에 저장됩니다 ... 그리고 char s []는 어떻습니까? = "hello"는 텍스트 세그먼트 부분에 먼저 저장되며 런타임 동안 Rickard가 답변에 언급 한대로 스택으로 복사합니다. 이 점을 분명히하십시오.
Nishant Kumar

2
@Nishant :이 char s[] = "hello"경우 "hello"배열은 초기화 방법을 컴파일러에 알려주는 이니셜 라이저 일뿐입니다. 예를 들어 s정적 저장 기간이있는 경우 텍스트 세그먼트에 해당 문자열이 생길 수도 있고 그렇지 않을 수도 있습니다. "hello"객체 의 유일한 인스턴스 는 초기화 된 데이터 세그먼트 에 있을 가능성이 높습니다 s. s자동 저장 기간이 있더라도 복사본 (예 :)이 아닌 일련의 리터럴 저장소로 초기화 될 수 있습니다 movl $1819043176, -6(%ebp); movw $111, -2(%ebp).
caf

더 정확하게 말하면, GCC 4.8은 이것을 .rodata링커 스크립트가와 같은 세그먼트에 덤프합니다 .text. 내 답변을 참조하십시오 .
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

@caf Rickard의 첫 번째 대답 char s[] = "Hello world";에서 리터럴 문자열을 읽기 전용 메모리에 넣고 스택의 새로 할당 된 메모리에 문자열을 복사하도록 작성되었습니다. 그러나 답은 읽기 전용 메모리에 넣은 리터럴 문자열에 대해서만 말하고 문장의 두 번째 부분은 생략합니다 copies the string to newly allocated memory on the stack. 그렇다면 두 번째 부분을 지정하지 않은 답변이 불완전합니까?
KPMG

1
@ AjaySinghNegi : 다른 의견 (이 답변과 Rickard의 답변에 언급)에서 언급했듯이 문자열 char s[] = "Hellow world";은 초기화 프로그램 일 뿐이며 반드시 별도의 읽기 전용 사본으로 저장되지는 ​​않습니다. 경우 s다음 정적 저장 기간이있는 경우에만 문자열을 복사의 위치에 읽기 - 쓰기 세그먼트에있을 가능성이 s, 심지어되지 다음 경우 컴파일러는로드 즉시 지시 또는 유사한로 배열을 초기화 할 선택이 아닌 복사 할 수 있습니다 읽기 전용 문자열에서. 요점은이 경우 이니셜 라이저 문자열 자체에는 런타임이 존재하지 않는다는 것입니다.
caf

60

선언이 주어지면

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 charC ++의) 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다른 배열을 가리 키도록 변수 를 재 할당 할 수 없습니다 .


2
가상 메모리 맵이 이해하기 쉽다고 생각합니다!
midnightBlue

32

C99 N1256 초안

문자열 리터럴에는 두 가지 용도가 있습니다.

  1. 초기화 char[]:

    char c[] = "abc";      

    이것은 "더 많은 마법"이며 6.7.8 / 14 "초기화"에 설명되어 있습니다.

    문자 유형의 배열은 선택적으로 중괄호로 묶인 문자열 리터럴로 초기화 될 수 있습니다. 문자열 리터럴의 연속 문자 (공간이 있거나 배열의 크기를 알 수없는 경우 종료 널 문자 포함)는 배열의 요소를 초기화합니다.

    따라서 이것은 바로 가기입니다.

    char c[] = {'a', 'b', 'c', '\0'};

    다른 일반 배열과 마찬가지로 c수정할 수 있습니다.

  2. 다른 곳에서는 :

    그래서 당신이 쓸 때 :

    char *c = "abc";

    이것은 다음과 유사합니다.

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;

    에서 암시 적 캐스트 주 char[]char *항상 합법적이다.

    그런 다음 수정 c[0]하면 __unnamedUB 도 수정 됩니다.

    이것은 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).


15
char s[] = "hello";

이니셜 라이저 (5 + 1 s) 를 보유하기에 충분히 긴 s배열 임을 선언 하고 주어진 문자열 리터럴의 멤버를 배열에 복사하여 배열을 초기화합니다.charchar

char *s = "hello";

s하나 이상의 (이 경우에는 더 많은)에 대한 포인터로 선언 char하고 리터럴을 포함하는 고정 된 (읽기 전용) 위치를 직접 가리 킵니다 "hello".


1
s가 변경되지 않는 경우 f (const char s []) 또는 f (const char * s) 함수에서 사용하는 것이 더 좋은 방법은 무엇입니까?
psihodelia

1
@ psihodelia : 함수 선언에는 차이가 없습니다. 두 경우 모두 s에 대한 포인터 const char입니다.
CB Bailey

4
char s[] = "Hello world";

여기에 s원하는 문자를 덮어 쓸 수있는 문자 배열이 있습니다.

char *s = "hello";

문자열 리터럴은이 포인터 s가 가리키는 메모리 어딘가에 이러한 문자 블록을 작성하는 데 사용 됩니다. 여기서는 객체를 변경하여 가리키는 객체를 재 할당 할 수 있지만, 문자열 리터럴을 가리키는 한은 변경할 수없는 문자 블록을 가리 킵니다.


@bo Persson 왜 두 번째 경우에 문자 블록을 변경할 수 없습니까?
Pankaj Mahato

3

또한 읽기 전용 목적으로 두 가지 사용이 동일하므로 다음과 같이 []또는 *(<var> + <index>) 형식으로 색인을 생성하여 char에 액세스 할 수 있습니다 .

printf("%c", x[1]);     //Prints r

과:

printf("%c", *(x + 1)); //Prints r

분명히, 당신이하려고하면

*(x + 1) = 'a';

읽기 전용 메모리에 액세스하려고 할 때 세그먼트 오류가 발생했을 수 있습니다.


이것은 x[1] = 'a';물론 플랫폼에 따라 segfault와 다를 바 없습니다 .
glglgl

3

추가하기 만하면 크기에 따라 다른 값을 얻을 수 있습니다.

printf("sizeof s[] = %zu\n", sizeof(s));  //6
printf("sizeof *s  = %zu\n", sizeof(s));  //4 or 8

위에서 언급했듯이 배열의 '\0'경우 최종 요소로 할당됩니다.


2
char *str = "Hello";

위의 설정은 메모리의 읽기 전용으로 플래그가 지정된 프로그램의 이진 이미지에 하드 코딩 된 리터럴 값 "Hello"를 가리 키도록 str을 설정합니다.이 문자열 리터럴의 모든 변경은 불법이며 세그먼트 오류를 ​​발생시킵니다.

char str[] = "Hello";

스택에서 새로 할당 된 메모리에 문자열을 복사합니다. 따라서 변경하는 것은 허용되며 합법적입니다.

means str[0] = 'M';

str을 "Mello"로 변경합니다.

자세한 내용은 비슷한 질문을 통해 진행하십시오.

"char * s"로 초기화되었지만 "char s []"가 아닌 문자열에 쓸 때 세그먼테이션 오류가 발생하는 이유는 무엇입니까?


0

다음의 경우 :

char *x = "fred";

x는 lvalue 이며 할당 할 수 있습니다. 그러나 다음의 경우 :

char x[] = "fred";

x는 lvalue가 아니라 rvalue이므로 할당 할 수 없습니다.


3
기술적 x으로 수정 불가능한 lvalue입니다. 그러나 거의 모든 상황에서 첫 번째 요소에 대한 포인터로 평가되며 해당 값은 rvalue입니다.
caf

0
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal

-1

주석에 비추어 볼 때 다음과 같은 점이 분명해야합니다. 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


이것은 모두 맞지만 질문과 관련이 없습니다. 그리고 C ++ 컴파일러에 대한 가정에 따르면 질문은 C ++이 아닌 C로 태그 지정됩니다.
Fabio는 Reinstate Monica가

char * s = "const string"에 대해서는 나쁘지 않습니다.
Paul Smith
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.