Programing

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

lottogame 2020. 6. 25. 08:03
반응형

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


치다:

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

나는 C Programming Language , 2nd Edition에서 위의 두 문장이 같은 것을하지 않는다는 것을 읽었습니다 .

나는 배열이 일부 데이터를 저장하기 위해 포인터를 조작하는 편리한 방법이라고 항상 생각했다. 그러나 이것은 사실이 아니다 ... C에서 배열과 포인터의 "사소한"차이점은 무엇인가?


사실이지만 미묘한 차이입니다. 본질적으로 전자는 :

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

멤버가 현재 범위의 스택 공간에있는 배열을 정의합니다.

char *pmessage = "now is the time";

현재 범위의 스택 공간에 있지만 다른 곳에서 메모리를 참조하는 포인터를 정의합니다 (이 시점에서 "현재 시간"은 메모리의 다른 곳에, 일반적으로 문자열 테이블로 저장 됨).

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

편집 : Mark, GMan 및 Pavel이 지적한 것처럼 주소 연산자가 이러한 변수 중 하나에 사용되는 경우에도 차이가 있습니다. 예를 들어, & pmessage는 char ** 유형의 포인터 또는 chars에 대한 포인터를 리턴하는 반면 & amessage는 char (*) [16] 유형의 포인터 또는 16 자의 배열에 대한 포인터를 리턴합니다 (예 : char **는 litb가 지적한대로 두 번 역 참조되어야합니다.


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

                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

문자열 리터럴 "현재 시간"은 메모리 주소 0x00008000에 char의 16 요소 배열로 저장됩니다. 이 메모리는 쓰기 가능하지 않을 수 있습니다. 그렇지 않다고 가정하는 것이 가장 좋습니다. 문자열 리터럴의 내용을 수정하려고 시도해서는 안됩니다.

선언

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

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

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

선언

char *pmessage = "now is the time";

메모리 주소 0x00500010에서 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가 메시지를 가리 키도록 변경 한 경우 :

pmessage = amessage;

메시지를 사용할 수있는 모든 곳에서 사용할 수 있습니다.


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

첫 번째는 짧은 말입니다

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

즉, 모든 문자를 포함하는 배열입니다. 특수 초기화가 자동으로 초기화되어 자동으로 크기를 결정합니다. 배열 요소는 수정 가능하므로 문자를 덮어 쓸 수 있습니다.

두 번째 형식은 포인터이며 문자를 가리 킵니다. 문자를 직접 저장하지 않습니다. 배열은 문자열 리터럴이므로 포인터를 가져 와서 가리키는 위치에 쓸 수 없습니다

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

이 코드는 아마도 상자에서 충돌했을 것입니다. 그러나 동작이 정의되지 않았기 때문에 원하는 것을 수행 할 수 있습니다.


다른 답변에 유용하게 추가 할 수는 없지만 Deep C Secrets 에서는 Peter van der Linden 이이 예제를 자세히 설명합니다. 이런 종류의 질문을한다면이 책을 좋아할 것입니다.


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


선언시 크기를 사용할 수 있도록 배열을 정의하면 배열 sizeof(p)/sizeof(type-of-array)의 요소 수를 반환합니다.


"지금은 시간입니다"라는 문자열에 대한 메모리가 두 개의 다른 위치에 할당되는 것과 함께, 배열 이름은 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

포인터는 메모리 주소를 보유하는 변수 일뿐입니다. 또 다른 문제인 "문자열 리터럴"이있는 playinf입니다. 차이점은 인라인으로 설명했습니다. 기본적으로 :

#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;
}

while &amessage과 같지만 and에 &amessage[0]대해서는 사실이 아님을 알 수 있습니다. 실제로, 문자열에 저장된 문자열 은 스택에있는 반면, 문자열은 다른 곳에있는 것으로 나타납니다.&pmessage&pmessage[0]amessagepmessage

마지막 printf는 문자열 리터럴의 주소를 보여줍니다. 컴파일러에서 "문자열 풀링"을 수행하는 경우 "지금은 시간입니다"라는 문자열의 사본이 하나만 있습니다. 주소가의 주소와 같지 않다는 것을 알 수 있습니다 amessage. 문자열이 초기화 될 때 문자열 amessage사본가져 오기 때문 입니다.

결국 요점은 amessage문자열을 자체 메모리 (이 예제에서는 스택)에 pmessage저장하고 다른 곳에 저장된 문자열 가리키는 것입니다.


The second one allocates the string in some read-only section of the ELF. Try the following:

#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);
}

and you will get a segfault on the second assignment (pmessage[3]='S').


differences between char pointer and array

C99 N1256 draft

There are two different uses of character string literals:

  1. Initialize char[]:

    char c[] = "abc";      
    

    This is "more magic", and described at 6.7.8/14 "Initialization":

    An array of character type may be initialized by a character string literal, optionally enclosed in braces. Successive characters of the character string literal (including the terminating null character if there is room or if the array is of unknown size) initialize the elements of the array.

    So this is just a shortcut for:

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

    Like any other regular array, c can be modified.

  2. Everywhere else: it generates an:

    So when you write:

    char *c = "abc";
    

    This is similar to:

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

    Note the implicit cast from char[] to char *, which is always legal.

    Then if you modify c[0], you also modify __unnamed, which is UB.

    This is documented at 6.4.5 "String literals":

    5 In translation phase 7, a byte or code of value zero is appended to each multibyte character sequence that results from a string literal or literals. The multibyte character sequence is then used to initialize an array of static storage duration and length just sufficient to contain the sequence. For character string literals, the array elements have type char, and are initialized with the individual bytes of the multibyte character sequence [...]

    6 It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

6.7.8/32 "Initialization" gives a direct example:

EXAMPLE 8: The declaration

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

defines "plain" char array objects s and t whose elements are initialized with character string literals.

This declaration is identical to

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

The contents of the arrays are modifiable. On the other hand, the declaration

char *p = "abc";

defines p with type "pointer to char" and initializes it to point to an object with type "array of char" with length 4 whose elements are initialized with a character string literal. If an attempt is made to use p to modify the contents of the array, the behavior is undefined.

GCC 4.8 x86-64 ELF implementation

Program:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

Compile and decompile:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

Output contains:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

Conclusion: GCC stores char* it in .rodata section, not in .text.

If we do the same for char[]:

 char s[] = "abc";

we obtain:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

so it gets stored in the stack (relative to %rbp).

Note however that the default linker script puts .rodata and .text in the same segment, which has execute but no write permission. This can be observed with:

readelf -l a.out

which contains:

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

The above answers must have answered your question. But I would like to suggest you to read the paragraph "Embryonic C" in The Development of C Language authored by Sir Dennis Ritchie.


For this line: char amessage[] = "now is the time";

the compiler will evaluate uses of amessage as a pointer to the start of the array holding the characters "now is the time". The compiler allocates memory for "now is the time" and initializes it with the string "now is the time". You know where that message is stored because amessage always refers to the start of that message. amessage may not be given a new value- it is not a variable, it is the name of the string "now is the time".

This line: char *pmessage = "now is the time";

declares a variable, pmessage which is initialized (given an initial value) of the starting address of the string "now is the time". Unlike amessage, pmessage can be given a new value. In this case, as in the previous case, the compiler also stores "now is the time" elsewhere in memory. For example, this will cause pmessage to point to the 'i' which begins "is the time". 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.

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

반응형