C: 할당되지 않은 포인터가 NULL이 아닌 예측 불가능한 메모리를 가리키는 이유는 무엇입니까?
오래 전에 나는 학교를 위해 C에서 프로그램을 짜곤 했습니다.제가 C에 대해 정말 싫어했던 것을 기억합니다. 할당되지 않은 포인터는 NULL을 가리키지 않습니다.
저는 선생님들을 포함한 많은 사람들에게 왜 그들이 할당되지 않은 포인터가 NULL을 가리키지 않는 기본 동작을 만드는지 물었습니다. 왜냐하면 그것이 예측할 수 없는 것이 훨씬 더 위험해 보이기 때문입니다.
답은 성능이었지만 저는 그것을 산 적이 없습니다.C가 NULL로 기본 설정했다면 프로그래밍 역사상 많은 버그를 피할 수 있었을 것이라고 생각합니다.
다음은 제가 말하는 것을 지적하기 위한 몇 가지 C 코드입니다.
#include <stdio.h>
void main() {
int * randomA;
int * randomB;
int * nullA = NULL;
int * nullB = NULL;
printf("randomA: %p, randomB: %p, nullA: %p, nullB: %p\n\n",
randomA, randomB, nullA, nullB);
}
(C 컴파일러가 제가 학교 다닐 때보다 훨씬 더 멋진 것을 보니 좋네요) 어떤 것을 컴파일하고 출력합니까?
랜덤 A: 0xb779eff4, 랜덤 B: 0x804844b, null A: (nil), null B: (nil)
사실, 그것은 포인터의 저장에 따라 다릅니다.정적 저장소가 있는 포인터는 null 포인터로 초기화됩니다.자동 저장 기간이 있는 포인터가 초기화되지 않았습니다.ISO C 996.7.8.10 참조:
자동 저장 기간이 있는 개체가 명시적으로 초기화되지 않으면 값이 결정되지 않습니다.정적 저장 기간이 있는 개체가 명시적으로 초기화되지 않은 경우:
- 포인터 유형이 있으면 null 포인터로 초기화됩니다.
- 산술 유형이 있는 경우 0으로 초기화됩니다(양 또는 부호 없음).
- 집합체인 경우, 모든 구성원은 본 규칙에 따라 (사전에) 초기화됩니다.
- 조합인 경우, 첫 번째 명명된 구성원은 이러한 규칙에 따라 (사전에) 초기화됩니다.
예, 자동 저장 기간이 있는 개체는 성능상의 이유로 초기화되지 않습니다.로깅 기능을 호출할 때마다 4K 어레이를 초기화한다고 상상해 보십시오(제가 작업한 프로젝트에서 확인한 내용). 다행히 초기화를 피할 수 있어 성능이 크게 향상되었습니다.
C에서 선언과 초기화는 의도적으로 다른 단계이기 때문입니다.그것들은 C가 그렇게 설계되었기 때문에 의도적으로 다릅니다.
함수 내부에서 이를 말할 때:
void demo(void)
{
int *param;
...
}
당신은 "친애하는 C 컴파일러, 당신이 이 함수를 위한 스택 프레임을 만들 때, 예약하는 것을 기억하세요.sizeof(int*)
포인터를 저장하기 위한 바이트."컴파일러는 그곳에 무슨 일이 있는지 묻지 않습니다. 그것은 당신이 곧 그것을 말할 것입니다. 않다면,에게 더 만약그지않다면렇더당아, 마도좋있언것을어입다니가은게신에)▁;▁if;)
아마도 안전한 스택 삭제 코드를 생성하는 것은 끔찍하게 어렵지 않을 것입니다.그러나 모든 함수 호출에 대해 호출되어야 하며, 많은 C 개발자들이 어쨌든 그들이 직접 그것을 채울 때 히트를 높이 평가할 것인지 의심스럽습니다.덧붙여서 스택을 유연하게 사용할 수 있다면 성능을 위해 할 수 있는 일이 많습니다.예를 들어 컴파일러는 최적화를 수행할 수 있습니다.
만약 당신이function1
다른 사람을 부릅니다.function2
을 저장하거나, 에 변수가 수 .function2
않는 것입니다.function2
우리가 여분의 공간을 만들 필요는 없잖아요, 그렇죠?스택의 동일한 부분을 두 개 모두에 사용합니다.이는 모든 사용 전에 스택을 초기화한다는 개념과 직접적으로 충돌합니다.
하지만 더 넓은 의미에서 (그리고 제가 생각하기에, 더 중요하게는) 그것은 절대적으로 필요한 것보다 더 많이 하지 않는 C의 철학과 일치합니다.이는 PDP11, PIC32MX(내가 사용하는 용도) 또는 Cray XT3에서 작업하는 경우에도 적용됩니다.그것이 바로 사람들이 다른 언어 대신 C를 사용하는 것을 선택할 수 있는 이유입니다.
- 추적이 없는 프로그램을 작성하려면
malloc
그리고.free
그럴 필요 없어요!메모리 관리가 제게 강요되지 않습니다! - 데이터 유니언을 비트패킹하고 타이핑하려면 할 수 있습니다! (물론 표준 준수에 대한 구현 지침을 읽기만 하면 됩니다.)
- 만약 내가 내 스택 프레임으로 무엇을 하고 있는지 정확히 안다면, 컴파일러는 나를 위해 다른 것을 할 필요가 없습니다!
간단히 말해서, C 컴파일러에게 점프를 요청할 때 얼마나 높은지 묻지 않습니다.결과 코드는 아마 다시 내려오지 않을 것입니다.
C에서 개발을 선택한 대부분의 사람들이 그렇게 좋아하기 때문에 변하지 않을 만큼의 관성을 가지고 있습니다.당신의 방법은 본질적으로 나쁜 생각이 아닐 수도 있고, 다른 많은 C 개발자들이 실제로 요청하지 않았을 뿐입니다.
퍼포먼스를 위해서입니다.
C는 60k가 일반적인 최대 메모리 용량이었던 PDP 11 즈음에 처음 개발되었습니다. 많은 사람들이 훨씬 적게 가지고 있을 것입니다.이러한 환경에서는 불필요한 할당 비용이 특히 많이 듭니다.
요즘에는 60k의 메모리가 무한대로 보이는 C를 사용하는 임베디드 장치가 많이 있습니다. PIC 12F675는 1k의 메모리를 가지고 있습니다.
이것은 당신이 포인터를 선언할 때, 당신의 C 컴파일러가 그것을 넣을 필요한 공간을 예약할 것이기 때문입니다.따라서 프로그램을 실행할 때 이 공간에는 이미 값이 있을 수 있습니다. 메모리의 이 부분에 할당된 이전 데이터 때문일 수 있습니다.
C 컴파일러는 이 포인터에 값을 할당할 수 있지만 코드의 일부에서 사용자 지정 값을 할당하는 것이 예외이므로 대부분의 경우 시간 낭비입니다.
그렇기 때문에 좋은 컴파일러는 변수를 초기화하지 않을 때 경고를 제공합니다. 그래서 저는 이 동작 때문에 버그가 그렇게 많지 않다고 생각합니다.당신은 경고문을 읽기만 하면 됩니다.
포인터는 이와 관련하여 특별하지 않습니다. 다른 유형의 변수는 초기화되지 않은 상태로 사용할 경우 동일한 문제가 발생합니다.
int a;
double b;
printf("%d, %f\n", a, b);
그 이유는 간단합니다. 초기화되지 않은 값을 알려진 값으로 설정하기 위해 런타임을 요구하면 각 함수 호출에 오버헤드가 추가됩니다.오버헤드는 단일 값으로는 많지 않을 수 있지만 포인터 배열이 많은 경우에는 다음을 고려하십시오.
int *a[20000];
함수의 시작 부분에 (포인터) 변수를 선언하면 컴파일러는 해당 변수로 사용할 레지스터를 따로 설정하거나 스택에 공간을 할당하는 두 가지 작업 중 하나를 수행합니다.대부분의 프로세서에서 스택의 모든 로컬 변수에 메모리를 할당하는 작업은 하나의 명령으로 수행됩니다. 이 명령은 모든 로컬 변수에 필요한 메모리 양을 계산하고 스택 포인터를 그만큼 아래로 당깁니다(또는 일부 프로세서에서 위로 밀어 올립니다).그 때 이미 메모리에 있는 것은 명시적으로 변경하지 않는 한 변경되지 않습니다.
포인터가 "임의" 값으로 "설정"되지 않습니다.할당하기 전에 스택 포인터(SP) 아래의 스택 메모리에는 이전에 사용한 모든 것이 포함됩니다.
.
.
SP ---> 45
ff
04
f9
44
23
01
40
.
.
.
로컬 포인터에 메모리를 할당한 후 변경된 것은 스택 포인터뿐입니다.
.
.
45
ff |
04 | allocated memory for pointer.
f9 |
SP ---> 44 |
23
01
40
.
.
.
이를 통해 컴파일러는 스택 포인터를 스택 아래로 이동시키는 하나의 명령어로 모든 로컬 변수를 할당할 수 있지만(스택 포인터를 다시 위로 이동시킴으로써 하나의 명령어로 모든 변수를 해제할 수 있습니다), 필요할 경우 사용자가 직접 초기화하도록 강제합니다.
C99에서는 코드와 선언을 혼합하여 초기화할 수 있을 때까지 코드의 선언을 연기할 수 있습니다.이렇게 하면 NULL로 설정할 필요가 없습니다.
첫째, 강제 초기화는 버그를 수정하지 않습니다.그들을 가립니다.유효한 값이 없는 변수(및 응용 프로그램에 따라 다름)를 사용하는 것은 버그입니다.
둘째, 자주 자신의 초기화를 수행할 수 있습니다.에 에.int *p;
글을 쓰다int *p = NULL;
또는int *p = 0;
.사용하다calloc()
보다는 (를 0으로 초기화함)malloc()
("0") 또는 0을 의미하는 것은 .아니요, 모든 비트 0이 NULL 포인터 또는 부동 소수점 값 0을 반드시 의미하는 것은 아닙니다.인 구현에 는 다음과 같습니다.
셋째, C(및 C++) 철학은 무언가를 빠르게 할 수 있는 수단을 제공하는 것입니다.여러분이 어떤 것을 하는 안전한 방법과 어떤 것을 하는 빠른 방법을 언어로 구현할 수 있는 선택권이 있다고 가정해 보겠습니다.더 많은 코드를 추가하여 안전한 방법을 더 빨리 만들 수는 없지만 그렇게 함으로써 더 안전한 방법을 만들 수 있습니다.또한 추가 검사 없이도 작업이 안전하도록 보장함으로써 작업을 빠르고 안전하게 수행할 수 있습니다. 물론 처음부터 빠른 옵션을 사용할 수 있다고 가정할 수 있습니다.
C는 원래 운영 체제와 관련 코드를 작성하도록 설계되었으며, 운영 체제의 일부는 가능한 한 빨리 작성해야 합니다.이것은 C에서 가능하지만, 더 안전한 언어에서는 그렇지 않습니다.게다가, C는 가장 큰 컴퓨터가 내 주머니에 있는 전화기보다 덜 강력할 때 개발되었습니다(오래되고 느려서 곧 업그레이드하려고 합니다).자주 사용하는 코드에 기계 주기를 몇 개 저장하면 가시적인 결과를 얻을 수 있습니다.
ninjalj가 설명한 것을 요약하면 예제 프로그램을 약간 변경하면 포인터가 실제로 NULL로 초기화됩니다.
#include <stdio.h>
// Change the "storage" of the pointer-variables from "stack" to "bss"
int * randomA;
int * randomB;
void main()
{
int * nullA = NULL;
int * nullB = NULL;
printf("randomA: %p, randomB: %p, nullA: %p, nullB: %p\n\n",
randomA, randomB, nullA, nullB);
}
내 기계에서 이 인쇄물이
랜덤 A: 00000000, 랜덤 B: 00000000, null A: 000000, null B: 00000000
메모리에 특정 값(0, NULL 등)이 포함되어야 할 이유가 없습니다.따라서, 이전에 특별히 작성되지 않은 경우, 메모리 위치는 사용자의 관점에서 임의의 값을 포함할 수 있습니다(그러나 바로 그 위치는 다른 소프트웨어에 의해 이전에 사용되었을 수 있으므로 카운터와 같은 해당 응용 프로그램에 의미 있는 값을 포함하지만 "사용자의" 관점에서 의미 있는 값을 포함합니다).는 단지 난수일 뿐입니다.특정 값으로 초기화하려면 하나 이상의 명령이 더 필요하지만, 이 초기화가 선행적으로 필요하지 않은 경우가 있습니다.v = malloc(x)
v의 초기 내용에 관계없이 v에 유효한 주소 또는 NULL을 할당합니다. 따라서 초기화하는 것은 시간 낭비로 간주될 수 있으며 C와 같은 언어는 priori를 수행하지 않도록 선택할 수 있습니다.물론 요즘은 이것이 거의 중요하지 않으며 초기화되지 않은 변수가 기본값(포인터의 경우 null, 지원되는 경우 0/0.0)을 갖는 언어도 있습니다.등등; 물론 느린 초기화는 할당 전에 액세스하는 경우에만 실제 초기화되기 때문에 100만 개의 요소 배열을 초기화하는 데 그렇게 많은 비용이 들지 않습니다.
임베디드 시스템을 제외하고는 이것이 기계 전원이 켜졌을 때 임의의 메모리 내용과 관련이 있다는 생각은 거짓입니다.가상 메모리와 다중 프로세스/다중 사용자 운영 체제가 있는 모든 시스템은 프로세스에 메모리를 제공하기 전에 메모리를 초기화합니다(일반적으로 0).그렇게 하지 않을 경우 심각한 보안 위반이 될 수 있습니다.자동 저장 변수의 '랜덤' 값은 동일한 프로세스에서 이전에 스택을 사용한 결과입니다.마찬가지로 malloc/new/등에 의해 반환되는 메모리의 '랜덤' 값은 동일한 프로세스의 이전 할당(이후 해제됨)에서 가져옵니다.
NULL을 가리키기 위해서는 자동으로 투명하게 수행된 경우에도 NULL이 할당되어야 합니다.
따라서 질문에 답하기 위해 포인터를 할당 해제 및 NULL로 둘 다 지정할 수 없는 이유는 포인터를 동시에 할당할 수 없기 때문입니다.
언급URL : https://stackoverflow.com/questions/3101896/c-why-do-unassigned-pointers-point-to-unpredictable-memory-and-not-point-to-nul
'programing' 카테고리의 다른 글
python: excel 워크북을 만들고 csv 파일을 워크시트로 덤프합니다. (0) | 2023.06.14 |
---|---|
compileSdkVersion과 targetSdkVersion의 차이점은 무엇입니까? (0) | 2023.06.14 |
입력이 C의 정수 유형인지 확인합니다. (0) | 2023.06.14 |
Firestore에서 모든 하위 컬렉션과 중첩된 하위 컬렉션이 있는 문서 삭제 (0) | 2023.06.14 |
Firebase 캐시를 바이패스하여(Android 앱에서) 데이터를 새로 고치는 방법은 무엇입니까? (0) | 2023.06.14 |