programing

C: 문자 포인터와 배열의 차이점

procenter 2022. 8. 17. 23:10
반응형

C: 문자 포인터와 배열의 차이점

고려사항:

char amessage[] = "now is the time";
char *pmessage = "now is the time";

C Programming Language, 2nd Edition에서 의 두 문장은 동일한 기능을 하지 않는다고 읽었습니다.

저는 항상 어레이가 포인터를 조작하여 데이터를 저장하는 편리한 방법이라고 생각했습니다만, 확실히 그렇지 않습니다.C의 어레이와 포인터의 "사소한" 차이는 무엇입니까?

다음은 두 선언의 결과를 보여주는 가상 메모리 맵입니다.

                0x00  0x01  0x02  0x03  0x04  0x05  0x06  0x07
    0x00008000:  'n'   'o'   'w'   ' '   'i'   's'   ' '   't'
    0x00008008:  'h'   'e'   ' '   't'   'i'   'm'   'e'  '\0'
        ...
amessage:
    0x00500000:  'n'   'o'   'w'   ' '   'i'   's'   ' '   't'
    0x00500008:  'h'   'e'   ' '   't'   'i'   'm'   'e'  '\0'
pmessage:
    0x00500010:  0x00  0x00  0x80  0x00

문자열 리터럴 "now is the time"은 메모리 주소 0x00008000에 char의 16 요소 배열로 저장됩니다.이 메모리는 기입할 수 없는 경우가 있습니다.기입할 수 없다고 가정하는 것이 가장 좋습니다.문자열 리터럴의 내용은 변경하지 마십시오.

선언문

char amessage[] = "now is the time";

는 메모리 주소 0x00500000에 16비트 배열의 char를 할당하고 문자열 리터럴의 내용을 복사합니다.이 메모리는 쓰기 가능합니다.메시지 내용을 마음껏 변경할 수 있습니다.

strcpy(amessage, "the time is now");

선언문

char *pmessage = "now is the time";

는 1개의 포인터를 메모리주소 0x00500010의 char에 할당하고 문자열 리터럴의 주소를 char에 복사합니다.

pmessage는 문자열 리터럴을 가리키므로 문자열 내용을 변경해야 하는 함수의 인수로 사용하지 마십시오.

strcpy(amessage, pmessage); /* OKAY */
strcpy(pmessage, amessage); /* NOT OKAY */
strtok(amessage, " ");      /* OKAY */
strtok(pmessage, " ");      /* NOT OKAY */
scanf("%15s", amessage);      /* OKAY */
scanf("%15s", pmessage);      /* NOT OKAY */

기타 등등.pmessage를 amessage를 가리키도록 변경한 경우:

pmessage = amessage;

그러면 메시지가 사용되는 모든 곳에서 사용할 수 있습니다.

맞아, 하지만 미묘한 차이점이야.기본적으로 전자는 다음과 같습니다.

char amessage[] = "now is the time";

멤버가 현재 스코프의 스택공간에 존재하는 배열을 정의합니다.

char *pmessage = "now is the time";

현재 스코프의 스택 공간에 존재하지만 메모리를 참조하는 포인터를 정의합니다(이 포인터에서는 "now is the time"은 일반적으로 문자열 테이블로 메모리의 다른 위치에 저장됩니다).

또한 두 번째 정의(명시적 포인터)에 속하는 데이터는 현재 스코프의 스택 공간에 저장되지 않으므로 저장 위치를 정확히 지정하지 않으므로 수정해서는 안 됩니다.

편집: Mark, GMan 및 Pavel이 지적한 바와 같이 주소 연산자가 이들 변수 중 하나에 사용되는 경우에도 차이가 있습니다.예를 들어 &pmessage는 char** 타입의 포인터 또는 chars 포인터에 대한 포인터를 반환하고 &amessage는 char(*)[16] 타입의 포인터 또는 16개의 chars 배열에 대한 포인터를 반환합니다(char**와 같이 litb가 지적할 때 2회 참조할 필요가 있습니다).

선언 시 어레이 크기를 사용할 수 있도록 어레이가 정의되어 있는 경우,sizeof(p)/sizeof(type-of-array)는 배열 내의 요소 수를 반환합니다.

배열에는 요소가 포함됩니다.포인터가 그들을 가리킵니다.

첫 번째는 짧은 형식의 말이다.

char amessage[16];
amessage[0] = 'n';
amessage[1] = 'o';
...
amessage[15] = '\0';

즉, 모든 문자를 포함하는 배열입니다.특수 초기화에 의해 초기화되고 크기가 자동으로 결정됩니다.배열 요소는 수정할 수 있습니다.그 안에 있는 문자를 덮어쓸 수 있습니다.

두 번째 형태는 단순히 글자를 가리키는 포인터입니다.이것은 문자를 직접 저장하지 않습니다.배열은 문자열 리터럴이므로 포인터를 가져와 가리키는 위치에 쓸 수 없습니다.

char *pmessage = "now is the time";
*pmessage = 'p'; /* undefined behavior! */

이 코드는 아마 당신의 박스에 크래시 될 것입니다.하지만 그 행동은 정의되지 않았기 때문에 원하는 것은 무엇이든 할 수 있다.

문자 포인터와 배열의 차이

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], 변경도 가능합니다.__unnamed(UB).

    이는 6.4.5 "String Literals"에서 문서화되어 있습니다.

    5 변환 단계7에서는 문자열 리터럴 또는 리터럴에서 생성되는 각 멀티바이트 문자 시퀀스에 값 0의 바이트 또는 코드가 부가됩니다.그런 다음 멀티바이트 문자 시퀀스를 사용하여 시퀀스를 포함하기에 충분한 정적 스토리지 기간 및 길이의 배열을 초기화합니다.문자열 리터럴의 경우 배열 요소는 type char를 가지며 멀티바이트 문자 시퀀스의 개별 바이트로 초기화됩니다[...]

    6 이들 어레이의 요소가 적절한 값을 가지고 있는 경우 이들 어레이가 구별되는지 여부는 명시되어 있지 않습니다.프로그램이 이러한 배열을 수정하려고 하면 동작은 정의되지 않습니다.

6.7.8/32 "초기화"는 다음과 같은 직접적인 예를 제시합니다.

예 8: 선언

char s[] = "abc", t[3] = "abc";

정의: "disposed" char 배열 객체s그리고.t문자열 리터럴로 요소가 초기화됩니다.

이 선언은 다음 선언과 동일합니다.

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

어레이의 내용은 변경할 수 있습니다.한편, 선언문은

char *p = "abc";

정의하다p"array 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실행은 되지만 쓰기 권한이 없는 세그먼트(segment)에 있습니다.이는 다음 방법으로 확인할 수 있습니다.

readelf -l a.out

다음 내용이 포함됩니다.

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

다른 답변에 유용하게 추가할 수는 없지만, Deep C Secrets에서 Peter van der Linden이 이 예를 자세히 다루고 있다는 것을 언급하겠습니다.이런 질문을 하신다면 이 책을 좋아하실 것 같습니다.


추신: 새로운 값을 할당할 수 있습니다.pmessage. 에 새로운 값을 할당할 수 없습니다.amessage그것은 불변의 것이다.

"now is the time" 문자열의 메모리가 2개의 다른 위치에 할당되는 것과 더불어 어레이 이름이 pmessage인 포인터 변수가 아닌 포인터 값으로 기능한다는 점도 유의해야 합니다.주요 차이점은 포인터 변수를 다른 위치를 가리키도록 수정할 수 있지만 배열은 수정할 수 없다는 것입니다.

char arr[] = "now is the time";
char *pchar = "later is the time";

char arr2[] = "Another String";

pchar = arr2; //Ok, pchar now points at "Another String"

arr = arr2; //Compiler Error! The array name can be used as a pointer VALUE
            //not a pointer VARIABLE

포인터는 메모리 주소를 유지하는 변수입니다.또 다른 문제인 string literals를 가지고 노는 것에 주의해 주세요.인라인으로 설명되는 차이점:기본적으로:

#include <stdio.h>

int main ()
{

char amessage[] = "now is the time"; /* Attention you have created a "string literal" */

char *pmessage = "now is the time";  /* You are REUSING the string literal */


/* About arrays and pointers */

pmessage = NULL; /* All right */
amessage = NULL; /* Compilation ERROR!! */

printf ("%d\n", sizeof (amessage)); /* Size of the string literal*/
printf ("%d\n", sizeof (pmessage)); /* Size of pmessage is platform dependent - size of memory bus (1,2,4,8 bytes)*/

printf ("%p, %p\n", pmessage, &pmessage);  /* These values are different !! */
printf ("%p, %p\n", amessage, &amessage);  /* These values are THE SAME!!. There is no sense in retrieving "&amessage" */


/* About string literals */

if (pmessage == amessage)
{
   printf ("A string literal is defined only once. You are sharing space");

   /* Demostration */
   "now is the time"[0] = 'W';
   printf ("You have modified both!! %s == %s \n", amessage, pmessage);
}


/* Hope it was useful*/
return 0;
}

첫 번째 폼(amessage)는 문자열 복사본을 포함하는 변수(배열)를 정의합니다."now is the time".

두 번째 폼(pmessage)는 문자열 복사와는 다른 위치에 있는 변수(포인터)를 정의합니다."now is the time".

이 프로그램을 사용해 보십시오.

#include <inttypes.h>
#include <stdio.h>

int main (int argc, char *argv [])
{
     char  amessage [] = "now is the time";
     char *pmessage    = "now is the time";

     printf("&amessage   : %#016"PRIxPTR"\n", (uintptr_t)&amessage);
     printf("&amessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&amessage[0]);
     printf("&pmessage   : %#016"PRIxPTR"\n", (uintptr_t)&pmessage);
     printf("&pmessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&pmessage[0]);

     printf("&\"now is the time\": %#016"PRIxPTR"\n",
            (uintptr_t)&"now is the time");

     return 0;
}

이면 알게 될 것이다&amessage와 동등하다&amessage[0]에 해당되지 않습니다.&pmessage그리고.&pmessage[0].실제로 문자열이 저장되어 있는 것을 알 수 있습니다.amessage이 문자열이 가리키는 동안 스택 상에 존재합니다.pmessage다른 곳에 산다.

마지막 printf는 문자열 리터럴의 주소를 나타냅니다.만약 당신의 컴파일러가 "string pooling"을 실행한다면, "now is the time" 문자열의 복사본은 하나뿐이며, 그 주소가 다음 주소와 동일하지 않음을 알 수 있습니다.amessage왜냐하면amessage는 초기화 시 문자열 복사본을 가져옵니다.

결국 요점은 말이다amessage는 문자열을 자체 메모리(이 예에서는 스택)에 저장합니다.pmessage는 다른 곳에 저장되어 있는 문자열을 가리킵니다.

위의 답변이 귀하의 질문에 대한 답변이어야 합니다.하지만 데니스 리치 경이 쓴 'C 언어 개발'의 '대사관 C' 단락을 읽어보시길 권합니다.

두 번째는 ELF의 읽기 전용 섹션에 문자열을 할당합니다.다음을 시도해 보십시오.

#include <stdio.h>

int main(char argc, char** argv) {
    char amessage[] = "now is the time";
    char *pmessage = "now is the time";

    amessage[3] = 'S';
    printf("%s\n",amessage);

    pmessage[3] = 'S';
    printf("%s\n",pmessage);
}

두 번째 과제(pmessage[3]='S')에서 세그먼트 폴트가 발생합니다.

이 행의 경우: char amessage [ ] = "지금이야말로 적기";

컴파일러는 "now is the time"이라는 문자를 가진 어레이의 선두에 대한 포인터로서 amessage 사용을 평가합니다.컴파일러는 "now is the time"을 위해 메모리를 할당하고 "now is the time" 문자열로 초기화합니다.메시지는 항상 메시지의 시작을 가리키기 때문에 메시지의 저장 위치를 알 수 있습니다.amessage에는 새로운 값을 지정할 수 없습니다.변수가 아니라 "now is the time" 문자열 이름입니다.

다음 행: char *pmessage = "now is the time";

now is the time 문자열 시작 주소의 초기화(초기값 지정)되는 변수 pmessage를 선언합니다.amessage와 달리 pmessage에는 새로운 값을 지정할 수 있습니다.이 경우 앞의 경우와 마찬가지로 컴파일러는 "now is the time"도 메모리에 저장합니다.예를 들어, 이로 인해 pmessage는 "is the time"으로 시작하는 "i"를 가리킵니다.pmessage = pmessage + 4;

Here is my summary of key differences between arrays and pointers, which I made for myself:

//ATTENTION:
    //Pointer depth 1
     int    marr[]  =  {1,13,25,37,45,56};      // array is implemented as a Pointer TO THE FIRST ARRAY ELEMENT
     int*   pmarr   =  marr;                    // don't use & for assignment, because same pointer depth. Assigning Pointer = Pointer makes them equal. So pmarr points to the first ArrayElement.

     int*   point   = (marr + 1);               // ATTENTION: moves the array-pointer in memory, but by sizeof(TYPE) and not by 1 byte. The steps are equal to the type of the array-elements (here sizeof(int))

    //Pointer depth 2
     int**  ppmarr  = &pmarr;                   // use & because going one level deeper. So use the address of the pointer.

//TYPES
    //array and pointer are different, which can be seen by checking their types
    std::cout << "type of  marr is: "       << typeid(marr).name()          << std::endl;   // int*         so marr  gives a pointer to the first array element
    std::cout << "type of &marr is: "       << typeid(&marr).name()         << std::endl;   // int (*)[6]   so &marr gives a pointer to the whole array

    std::cout << "type of  pmarr is: "      << typeid(pmarr).name()         << std::endl;   // int*         so pmarr  gives a pointer to the first array element
    std::cout << "type of &pmarr is: "      << typeid(&pmarr).name()        << std::endl;   // int**        so &pmarr gives a pointer to to pointer to the first array elelemt. Because & gets us one level deeper.

An array is a const pointer. You cannot update its value and make it point anywhere else. While for a pointer you can do.

ReferenceURL : https://stackoverflow.com/questions/1335786/c-differences-between-char-pointer-and-array

반응형