programing

c의 메모리에 대한 포인터와 액세스.조심하세요.

linuxpc 2023. 9. 13. 22:22
반응형

c의 메모리에 대한 포인터와 액세스.조심하세요.

아직도 C를 더 배우고 있고 조금 혼란스럽습니다.참고문헌을 보면 초기화되지 않은 포인터를 할당하는 것에 대한 주의사항이 있습니다.그들은 계속해서 예를 들어요.어제 조언을 해주신 분들로부터 좋은 답변을 받았습니다.

우선 순위, 괄호, 반복 배열 함수가 있는 포인터

후속 작업에서 저는 루프의 마지막 반복과 잠재적으로 존재하지 않는 장소를 가리키는 포인터에 대해 간략하게 물었습니다(즉, 참조가 이에 대해 경고했기 때문에).그래서 다시 찾아보니 다음과 같습니다.

포인터가 있는 경우

int *pt;

그런 다음 초기화하지 않고 사용합니다. (즉, 나는 이것을 다음과 같은 문장 없이 의미로 받아들입니다.)*pt= &myVariable):

*pt = 606;

이 포인터가 메모리의 어느 위치에 할당되었는지에 따라 결국 정말 안 좋은 날이 될 수 있습니다.제가 고민하는 부분은 일련의 캐릭터로 작업할 때 이와 같은 것이 좋습니다.

char *str = "Sometimes I feel like I'm going crazy.";

참조에서 "메모리의 어느 위치에 문자열이 할당되었는지 걱정하지 마십시오. 컴파일러가 자동으로 처리합니다."라고 말합니다.따라서 초기화는 말할 필요도 없습니다.*str = &str[0];아니면*str = str;으로 . , char str[n];뒤에서?

이것은 왜 다르게 취급되는 겁니까?아니면, 제가 완전히 오해하고 있는 건가요?

이 경우:

char *str = "Sometimes I feel like I'm going crazy.";

를 초기화하고 str지정된 문자열 리터럴의 주소를 포함합니다.이 시점에서 당신은 실제로 어떤 것도 언급하지 않고 있습니다.

이것도 괜찮습니다.

char *str;
str = "Sometimes I feel like I'm going crazy.";

왜냐하면 당신이 당신에게 할당하고 있기 때문입니다.str실제로 그것을 재참조한 것은 아닙니다.

문제는 다음과 같습니다.

int *pt;
*pt = 606;

왜냐하면 ㅇpt초기화되지 않은 다음 역참조됩니다.

또한 동일한 이유로 이 작업을 수행할 수 없습니다(게다가 유형이 일치하지 않음).

*pt= &myVariable;

하지만 이렇게 할 수 있습니다.

pt= &myVariable;

이후 자유롭게 사용하실 수 있습니다.*pt.

이 ㅇㅇㅇ을 sometype *p = something; 합니다.sometype *p; p = something;,것은 아니다.sometype *p; *p = something;이 문자열 리터럴을으로 사용하면 합니다. 그것은 당신이 문자열 리터럴을 그렇게 사용할 때, 컴파일러가 그것을 어디에 놓을 것인지를 결정하고 거기에 주소를 넣는다는 것을 의미합니다.

성명서

char *str = "Sometimes I feel like I'm going crazy.";

와 동치입니다.

char *str;
str = "Sometimes I feel like I'm going crazy.";

문자열 리터럴을 단순화하면 다음과 같이 표현할 수 있습니다.

const char literal[] = "Sometimes I feel like I'm going crazy.";

그래서 표현이

char *str = "Sometimes I feel like I'm going crazy.";

논리적으로 다음과 동치입니다.

const char literal[] = "Sometimes I feel like I'm going crazy.";
const char *str = literal;

물론 문학자들은 이름을 가지고 있지 않습니다.

그러나 실제 개체에 대한 메모리가 할당되어 있지 않은 문자 포인터는 참조를 취소할 수 없습니다.

/* Wrong */
char *c;
*c = 'a';
/* Wrong  - you assign the pointer with the integer value */ 
char *d = 'a';

/* Correct  */
char *d = malloc(1);
*d = 'a';

/* Correct */
char x
char *e = &x;
*e = 'b';

마지막 예:

/* Wrong - you assign the pointer with the integer value */
int *p = 666;

/* Wrong you dereference the pointer which references to the not allocated space */
int *r;
*r = 666;

/* Correct */
int *s = malloc(sizeof(*s));
*s = 666;

/* Correct */
int t;
int *u = &t;
*u = 666;

그리고 마지막은 - 현악기 리터럴과 비슷한 = 복합 리터럴:

/* Correct */
int *z = (int[]){666,567,234};
z[2] = 0;
*z = 5;

/* Correct */
int *z = (const int[]){666,567,234}; 

그 예를 생각해 내느라 수고했어요.는를잘다예의(다예잘e:를ta는를gse(n :char *text;합니다. 에 :text = "Hello, World!";).

쓸 때:

char *text = "Hello!";

이는 다음과 같이 말하는 것과 본질적으로 같습니다.

char *text;        /* Note the '*' before text */
text = "Hello!";   /* Note that there's no '*' on this line */

(당신도 알다시피, 첫번째 줄은 다음과 같이 쓸 수 있습니다.char* text;.)

그럼 왜 없는 거지?*두번째 줄에서?왜냐하면 ㅇtext는 입니다.char*!"는 또한 "Hello!" 입니다.char*여기에는. 여기에는 이견이 없습니다.

또한 컴파일러에 관한 한 다음 세 줄은 동일합니다.

char *text = "Hello!";
char* text = "Hello!";
char * text = "Hello!";

의 는 의 에 합니다 합니다 앞 또는 뒤의 공간 배치*차이가 전혀 없다.두은과은을기에의이가다더째가이더다ettee두의srtd에e,s째oysd은기과을text는 ㅇchar* (하지만 조심하세요!이 스타일은 한 줄에 두 개 이상의 변수를 선언할 경우 사용자를 태울 수 있습니다.)

다음과 같은 경우:

int *pt;
*pt = 606;   /* Unsafe! */

라고 말할지도 모릅니다.*pt는 ㅇint, 그것도 마찬가지입니다606 합니다 합니다 pt(*)는 int를 포함해야 하는 메모리에 대한 포인터입니다.반면에.*pt((으)로*)는 기억 속에 들어있는 인트를 말합니다.pt(et*가 가리키고 .)가 가리키고 있습니다.

로부터pt이다를 사용하여 적이 .*pt(할당 또는 참조 해제 중 하나) 안전하지 않습니다.

자, 대사에 대한 흥미로운 부분은.

int *pt;
*pt = 606;   /* Unsafe! */

그들이 (아마도 경고와 함께) 컴파일할 것입니다.에를 보기 입니다.*pt…의 한 int,그리고.606…의 한 int또한, 이견이 없습니다.쓰여 있는 대로 나,진,는pt어떤 유효한 메모리도 가리키지 않아요, 그래서 다음에 할당합니다.*pt충돌을 일으키거나, 데이터가 손상되거나, 세상의 종말을 초래할 가능성이 있습니다.

중요한 것은 우리가 그 일을*pt는 변수가 아닙니다(종종 1처럼 사용됨).*pt가된의을다다sne에 된 메모리의 을 말합니다.pt. 든 간에*pt안전하게 사용할 수 있는지 여부에 따라 달라집니다.pt유효한 메모리 주소를 포함합니다. 만약pt메모리로한로지다면어다fen로지한어,neo'y*pt안전하지 않습니다.

그래서 이제 여러분은 궁금해 할지도 모릅니다.이 일을 선언한 이유가 무엇입니까?pt…의 한 int*int?

경우에 따라 다르지만, 많은 경우 의미가 없습니다.

C와 C++로 프로그래밍할 때는 다음과 같은 조언을 사용합니다.변수를 포인터로 만들지 않고 선언할 수 있다면 포인터로 선언하지 않는 것이 좋습니다.

프로그래머들은 필요 없을 때 포인터를 사용하는 경우가 아주 많습니다.그 당시 그들은 다른 방법을 생각하고 있지 않습니다.제 경험으로는 포인터를 사용하지 않는 것이 사람들의 주의를 끌면, 그들은 종종 포인터를 사용하지 않는 것이 불가능하다고 말할 것입니다.그리고 제가 그들을 다르게 증명하면, 그들은 보통 역추적해서 (포인터를 사용하는) 그들의 코드가 포인터를 사용하지 않는 코드보다 더 효율적이라고 말할 것입니다.

(그러나 모든 프로그래머들에게 해당되지는 않습니다.어떤 사람들은 포인터를 비 포인터로 교체하는 것의 매력과 단순함을 인식하고 기꺼이 코드를 변경할 것입니다.)

물론 모든 경우에 대해 말할 수는 없지만 요즘의 C 컴파일러들은 효율성 측면에서 포인터 코드와 비포인터 코드를 모두 실질적으로 동일하게 컴파일할 수 있을 정도로 똑똑합니다.뿐만 아니라 경우에 따라 포인터를 사용하는 코드보다 비 포인터 코드가 더 효율적인 경우가 많습니다.

예제에서 혼합한 개념은 4가지입니다.

  1. 포인팅을 선언합니다. int *p;아니면char *str;입니다.
  2. 선언 시 포인터를 초기화합니다. char *str = "some string";포인터를 선언하고 초기화합니다.
  3. 포인터에 값을 할당합니다. str = "other string";포인터에 값을 할당합니다.유사하게p = (int*)606;606이라는 값을 포인터에 할당합니다.그러나 첫 번째 경우 값은 합법적이며 정적 메모리에서 문자열의 위치를 가리킵니다.두째에의를다다를에 할당합니다.p. 법적 주소일 수도 있고 아닐 수도 있기 때문에.p = &myint;아니면p = malloc(sizeof(int));더 나은 선택입니다
  4. 포인터가 가리키는 것에 값을 할당합니다. *p = 606;값을 '포인트티'에 할당합니다.이제 포인터 'p'의 값이 합법적인지 아닌지에 따라 달라집니다.포인터를 초기화하지 않은 경우에는 (운이 좋은 경우가 아니라면) 불법입니다.

여기에 좋은 설명들이 많습니다.OP가 요청했습니다.

이것은 왜 다르게 취급되는 겁니까?

그것은 정당한 질문입니다. 그는 방법이 아니라 이유를 의미합니다.

단답형

그것은 디자인 결정입니다.

장답

할당에서 리터럴을 사용할 때 컴파일러는 생성된 어셈블리 명령에 리터럴을 배치하거나(다른 리터럴 바이트 길이를 수용하도록 가변 길이 어셈블리 명령을 허용할 수도 있음) CPU가 도달할 수 있는 곳에 리터럴을 배치합니다(메모리, 레지스터...).int인 것 의 경우...s, 에 은 인 에는 의 하는 에는 의 프로그램(?)에 사용되는 거의 모든 문자열이 너무 길어서 어셈블리 명령에 배치할 수 없습니다.임의로 긴 조립 명령어가 범용 CPU에 좋지 않다는 점을 고려하여 C 설계자는 문자열에 이 사용 사례를 최적화하고 프로그래머에게 메모리를 할당하여 한 단계 더 절약하기로 결정했습니다.이와 같은 방식으로 동작은 기계 전체에서 일관됩니다.

반례 다른 언어의 경우 반드시 그렇지는 않은지 확인해 보십시오.저기(파이썬입니다),int상수들은 실제로 메모리에 배치되고 항상 id가 주어집니다.따라서 동일한 리터럴이 할당된 두 개의 다른 변수의 주소를 가져오려고 하면 동일한 값을 반환합니다.id(Python Loader에 의해 메모리에 이미 저장된 동일한 리터럴을 참조하고 있기 때문에).Python에서는다서nt은tseosn서,id는 파이썬의 추상 머신에 있는 주소에 해당합니다.

메모리의 각 바이트는 자신의 번호가 매겨진 비둘기 구멍에 저장됩니다.그 숫자는 해당 바이트의 "주소"입니다.

프로그램이 컴파일되면 상수의 데이터 테이블이 작성됩니다.런 타임에 이것들은 메모리 어딘가에 복사됩니다.따라서 실행 시 메모리에 문자열(여기서 100,000번째 바이트)이 있습니다.

@100000 Sometimes I feel like I'm going crazy.\0

는 과 를 했습니다 했습니다 를 등의 코드를 했습니다.str가 생성되고 해당 문자열이 저장될 주소로 자동으로 초기화됩니다. 이 예의 이의는서는o이서'nstr -> 100000. 여기서 이름 포인터가 나온 거지str는 실제로 문자열 데이터를 포함하지 않으며, 문자열 데이터의 주소(즉, 숫자)를 보유하고 있으며, "이 주소에 있는 데이터 조각"이라고 말합니다.

그래서 만약에str되었으며,고이어을다그다e 합니다.100000.

때, 를 들어 를 과 할 과 를 할 *str = '\0', 이런 말이 나옵니다.기억의 초점은 이 '\0'을 거기에 놓으라는 것입니다.

따라서 코드가 포인터를 정의하지만 초기화 없이는 어디를 가리킬 수 있으며, 심지어 실행 파일이 소유하고 있지 않은(또는 소유하고 있지만 쓸 수 없는) 메모리를 가리키기도 합니다.

예를 들어,

int *pt = blah;  // What does 'pt' point at?

주소가 없습니다.코드가 참조를 취소하려고 하면 기억의 어느 곳을 가리킬 뿐이고, 이것은 불확정한 결과를 제공합니다.

그러나 다음의 경우:

int number = 605;
int *pt    = &number

*pt = 606;

가 가 을 했기 에 하게 합니다 할 합니다 하게 의 저장 공간을 합니다.number, 그리고 지금pt에는 해당 공간의 주소가 들어 있습니다.

연산자의 주소 의 를 할 할 를 을&변수의 내용이 저장된 메모리의 번호를 알려줍니다. 만약 서약가가eo서number된에 .byte 100040:

int number = 605;
printf( "Number is stored at %p\n", &number );

결과는 다음과 같습니다.

Number is stored at 100040

스트링 배열과 마찬가지로, 이것들은 정말로 단지 포인터일 뿐입니다.주소는 첫 번째 요소의 메모리 번호입니다.

// words, words_ptr1, words_ptr2 all end up being the same address
char words[] = "Sometimes I feel like I'm going crazy."
char *words_ptr1 = &(words[0]);
char *words_ptr2 = words;

여기에 아주 좋고 상세한 정보가 있는 답변들이 있습니다.OP 쪽으로 좀 더 직설적으로 공략해서 다른 답변을 올리겠습니다.다시 표현하기:

왜가.

int *pt;
*pt = 606;

not ok (non working case), 및

char *str = "Sometimes I feel like I'm going crazy.";

(실무) 괜찮습니까?

다음을 고려합니다.

  1. char *str = "Sometimes I feel like I'm going crazy.";
    

    와 동치입니다.

    char *str;
    str = "Sometimes I feel like I'm going crazy.";
    
  2. 가장 가까운 "유사한" 작업 사례intis ( 문자열 리터럴 대신 복합 리터럴 사용)

    int *pt = (int[]){ 686, 687 };
    

    아니면

    int *pt;
    pt = (int[]){ 686, 687 };
    

따라서 비업무 사례와 다른 점은 세 가지입니다.

  1. 사용하다pt = ...에 대신에*pt = ...

  2. 값이 아닌 복합 리터럴을 사용합니다(같은 토큰으로 str = 'a'는 작동하지 않음).

  3. 복합 리터럴은 저장 수명이 표준/구현에 따라 달라지기 때문에 항상 작동이 보장되는 것은 아닙니다.실제로 위와 같이 사용하면 컴파일 오류가 발생할 수 있습니다.

를 로 할 의 배열로 선언할 수 있습니다.char txt[] 문자 는를다자a를rg자는rr합니다 .char* txt 다음은 문자열의 선언 및 초기화를 보여줍니다.

char* txt = "Hello";

C string literal

위에 된 바와 같이, ① ③ ④ ⑤ ⑥ ⑦ 와 한 txt는 문자열 리터럴의 첫 번째 문자를 가리키는 포인터입니다.

문자열 변수를 수정(읽기/쓰기)할 수 있는지 여부는 선언한 방법에 따라 달라집니다.

6 String .4.5 (ISO)
이 서로 는 해당 값이 에 알 수 .6 이러한 배열의 요소가 적절한 값을 갖는 경우 이들 배열이 구별되는지 여부는 지정되지 않습니다.프로그램이 이러한 배열을 수정하려고 하면 동작이 정의되지 않습니다.

만약 한다면, 가 을 한다면 한다면 을 !txt 섹션가던는용터서기열다을할열다할n을eeae서leg가e터nlsr-y ..rodata) 하는)이라도)txt로지다다되지 로 선언되지 않습니다.const char*할 수 없습니다. 그래서 우리는 수정할 수 없습니다.사실 우리는 그것을 수정하려고 시도하지 말아야 합니다. 경우 ③gcc경고를 발사할 수 있습니다(-Wwrite-strings)로 인해 실패하기도 합니다.-Werror 이 경우 문자열 변수를 const 포인터로 선언하는 것이 좋습니다.

const char* txt = "Hello";

반면 문자열 변수를 문자 배열로 선언할 수 있습니다.

char txt[] = "Hello";

그 경우 컴파일러가 배열을 문자열 리터럴에서 초기화할 수 있도록 정렬하므로 수정할 수 있습니다.

참고: 문자 배열은 첫 번째 문자를 가리키는 포인터처럼 사용할 수 있습니다.그래서 우리가 사용할 수 있는 것입니다.txt[0]아니면*txt첫 번째 문자에 액세스하는 구문입니다.문자 배열을 포인터로 명시적으로 변환할 수도 있습니다.

char txt[] = "Hello";
char* ptxt = (char*) txt;

언급URL : https://stackoverflow.com/questions/54025987/pointers-and-access-to-memory-in-c-be-careful

반응형