C 문자열에서 '\ 0'뒤의 메모리는 어떻게됩니까?
놀랍게도 간단하고 / 멍청하고 / 기본적인 질문이지만 모르겠습니다. 함수의 사용자에게 함수 시작 부분에서 길이를 알 수없는 C- 문자열을 반환한다고 가정합니다. 처음에는 길이에 상한 만 배치 할 수있어 가공에 따라서는 사이즈가 줄어드는 경우가 있습니다.
문제는 충분한 힙 공간 (상한)을 할당 한 다음 처리 중에 문자열을 종료하는 데 문제가 있습니까? 즉, 할당 된 메모리의 중간에 '\ 0'을 붙이면 (a.) free()여전히 제대로 작동하고 (b.) '\ 0'뒤의 공간이 중요하지 않습니까? '\ 0'이 추가되면 메모리가 그냥 반환 free()됩니까? 아니면 호출 될 때까지 거기에 앉아 있습니까? malloc을 호출하기 전에 필요한 공간을 계산하는 초기 프로그래밍 시간을 절약하기 위해이 교수형 공간을 거기에 두는 것이 일반적으로 나쁜 프로그래밍 스타일입니까?
여기에 컨텍스트를 제공하기 위해 다음과 같이 연속 중복을 제거하고 싶다고 가정 해 보겠습니다.
"Hello oOOOo !!"입력 -> 출력 "Helo oOo!"
... 그리고 내 작업으로 인한 크기를 사전 계산하는 방법을 보여주는 아래의 일부 코드는 힙 크기를 올바르게 얻기 위해 효과적으로 두 번 처리를 수행합니다.
char* RemoveChains(const char* str)
{
if (str == NULL) {
return NULL;
}
if (strlen(str) == 0) {
char* outstr = (char*)malloc(1);
*outstr = '\0';
return outstr;
}
const char* original = str; // for reuse
char prev = *str++; // [prev][str][str+1]...
unsigned int outlen = 1; // first char auto-counted
// Determine length necessary by mimicking processing
while (*str) {
if (*str != prev) { // new char encountered
++outlen;
prev = *str; // restart chain
}
++str; // step pointer along input
}
// Declare new string to be perfect size
char* outstr = (char*)malloc(outlen + 1);
outstr[outlen] = '\0';
outstr[0] = original[0];
outlen = 1;
// Construct output
prev = *original++;
while (*original) {
if (*original != prev) {
outstr[outlen++] = *original;
prev = *original;
}
++original;
}
return outstr;
}
할당 된 메모리 중간에 '\ 0'을 붙이면
(a.) free () 여전히 제대로 작동하고
예.
(b.) '\ 0'뒤의 공백이 중요하지 않습니까? '\ 0'이 추가되면 메모리가 반환됩니까? 아니면 free ()가 호출 될 때까지 공간을 차지하고 있습니까?
의존합니다. 종종 많은 양의 힙 공간을 할당 할 때 시스템은 먼저 가상 주소 공간을 할당합니다. 페이지에 쓸 때 실제 물리적 메모리가 할당되어 나중에 OS에 가상 메모리가있을 때 디스크로 스왑 될 수 있습니다. 지원하다). 유명하게도, 가상 주소 공간의 낭비적인 할당과 실제 물리적 / 스왑 메모리 사이의 이러한 차이로 인해 희소 어레이가 그러한 OS에서 합리적으로 메모리 효율적이 될 수 있습니다.
이제이 가상 주소 지정 및 페이징의 세분성은 메모리 페이지 크기에 있습니다. 4k, 8k, 16k ...? 대부분의 OS에는 페이지 크기를 확인하기 위해 호출 할 수있는 기능이 있습니다. 따라서 작은 할당을 많이 수행하는 경우 페이지 크기로 반올림하는 것은 낭비이며 실제로 사용해야하는 메모리 양에 비해 제한된 주소 공간이있는 경우 위에서 설명한 방식으로 가상 주소 지정에 따라 달라집니다. 확장되지 않습니다 (예 : 32 비트 주소가 지정된 4GB RAM). 반면에 32GB RAM으로 실행되는 64 비트 프로세스가 있고 이러한 문자열 할당을 비교적 적게 수행하는 경우 사용할 가상 주소 공간이 엄청나게 많고 페이지 크기로 반올림됩니다. t는 많은 양입니다.
그러나-버퍼 전체에 기록한 다음 이전 시점에서 종료하는 경우 (이 경우 한 번 기록 된 메모리에 백업 메모리가 있고 결국 스왑 상태가 될 수 있음)와 쓰기 만하는 큰 버퍼를 갖는 것의 차이에 유의하십시오. 그런 다음 첫 번째 비트로 종료됩니다 (이 경우 백업 메모리는 페이지 크기로 반올림 된 사용 된 공간에 대해서만 할당됩니다).
또한 많은 운영 체제에서 힙 메모리는 프로세스가 종료 될 때까지 운영 체제로 반환되지 않을 수 있습니다. 대신 malloc / free 라이브러리는 힙을 늘릴 필요가있을 때 OS에 알립니다 (예 : sbrk()UNIX 또는 VirtualAlloc()Windows에서 사용). ). 그런 의미에서 free()메모리는 프로세스를 재사용 할 수있는 여유 공간이 있지만 다른 프로세스가 사용할 수있는 공간은 아닙니다. 일부 운영 체제는이를 최적화합니다. 예를 들어, 매우 큰 할당을 위해 별개의 독립적으로 해제 가능한 메모리 영역을 사용합니다.
malloc을 호출하기 전에 필요한 공간을 계산하는 초기 프로그래밍 시간을 절약하기 위해이 교수형 공간을 거기에 두는 것이 일반적으로 나쁜 프로그래밍 스타일입니까?
다시 말하지만, 얼마나 많은 할당을 처리하는지에 따라 다릅니다. 가상 주소 공간 / RAM에 상대적으로 많은 수가있는 경우-를 사용하여 원래 요청 된 모든 메모리가 실제로 필요하지 않다는 것을 메모리 라이브러리에 명시 적으로 알리 realloc()거나 strdup()실제를 기반으로 새 블록을 더 엄격하게 할당하는 데 사용할 수도 있습니다. 필요 ( free()원래)-malloc / free 라이브러리 구현에 따라 더 좋거나 나쁠 수 있지만, 그 차이로 인해 크게 영향을받는 애플리케이션은 거의 없습니다.
때때로 코드는 호출 애플리케이션이 관리 할 문자열 인스턴스 수를 추측 할 수없는 라이브러리에있을 수 있습니다. 이러한 경우에는 너무 나 빠지지 않는 느린 동작을 제공하는 것이 좋습니다. 따라서 메모리 블록을 원래 문자열 버퍼의 알 수없는 비율이 낭비되는 대신 (병리학 적 경우-임의의 큰 할당 후 0 또는 1 개의 문자가 사용됨) 대신 문자열 데이터에 적합합니다 (추가 작업의 집합이므로 big-O 효율성에 영향을주지 않음). 성능 최적화를 위해 사용하지 않은 공간이 사용 된 공간보다 크면 메모리를 반환해야합니다. 취향에 맞게 조정하거나 호출자가 구성 할 수 있도록합니다.
다른 답변에 대해 언급합니다.
그렇다면 재 할당이 더 오래 걸릴지 또는 전처리 크기 결정이 걸리는지 판단하는 것이 중요합니까?
성능이 최우선이라면 예-프로파일 링을 원할 것입니다. CPU 바운드가 아니라면 일반적으로 "전처리"히트를 취하고 적절한 크기의 할당을 수행합니다. 조각화와 엉망이 적습니다. 이에 반하여 일부 기능에 대한 특수 전처리 모드를 작성해야하는 경우 오류 및 코드를 유지 관리 할 추가 "표면"입니다. (이 트레이드 오프 결정은 일반적으로 asprintf()에서 직접 구현할 때 필요 snprintf()하지만 적어도 snprintf()문서화 된대로 작동 할 수 있으며 개인적으로 유지할 필요가 없습니다).
'\ 0'이 추가되면 메모리가 반환됩니까? 아니면 free ()가 호출 될 때까지 공간을 차지하고 있습니까?
에 대해 마법 같은 것은 없습니다 \0. realloc할당 된 메모리를 "줄이려면" 호출 해야합니다. 그렇지 않으면 전화를 걸 때까지 메모리가 그대로 유지됩니다 free.
할당 된 메모리 중간에 '\ 0'을 붙이면 (a.) free ()가 여전히 제대로 작동합니까?
그 메모리에서 무엇을 하든지간에 에서free 반환 한 똑같은 포인터를 전달하면 항상 제대로 작동합니다 malloc. 물론 밖에서 글을 쓰면 모든 베팅이 해제됩니다.
\0관점 malloc과 free관점 에서 볼 때 하나 이상의 캐릭터 일 뿐이며 메모리에 어떤 데이터를 넣는 지 신경 쓰지 않습니다. 따라서 중간에 free추가하든 \0전혀 추가하지 않든 여전히 작동합니다 \0. 할당 된 추가 공간은 그대로 유지 \0되며 메모리에 추가하자마자 프로세스로 다시 반환되지 않습니다 . 개인적으로 리소스를 낭비하는 상한선을 할당하는 대신 필요한 양의 메모리 만 할당하는 것을 선호합니다.
malloc ()을 호출하여 힙에서 메모리를 얻으면 메모리를 사용할 수 있습니다. \ 0을 삽입하는 것은 다른 문자를 삽입하는 것과 같습니다. 이 메모리는 해제 할 때까지 또는 OS가 다시 요구할 때까지 소유합니다.
The \0is a pure convention to interpret character arrays as stings - it is independent of the memory management. I.e., if you want to get your money back, you should call realloc. The string does not care about memory (what is a source of many security problems).
malloc just allocates a chunk of memory .. Its upto you to use however you want and call free from the initial pointer position... Inserting '\0' in the middle has no consequence...
To be specific malloc doesnt know what type of memory you want (It returns onle a void pointer) ..
Let us assume you wish to allocate 10 bytes of memory starting 0x10 to 0x19 ..
char * ptr = (char *)malloc(sizeof(char) * 10);
Inserting a null at 5th position (0x14) does not free the memory 0x15 onwards...
However a free from 0x10 frees the entire chunk of 10 bytes..
free()will still work with a NUL byte in memorythe space will remain wasted until
free()is called, or unless you subsequently shrink the allocation
Generally, memory is memory is memory. It doesn't care what you write into it. BUT it has a race, or if you prefer a flavor (malloc, new, VirtualAlloc, HeapAlloc, etc). This means that the party that allocates a piece of memory must also provide the means to deallocate it. If your API comes in a DLL, then it should provide a free function of some sort. This of course puts a burden on the caller right? So why not put the WHOLE burden on the caller? The BEST way to deal with dynamically allocated memory is to NOT allocate it yourself. Have the caller allocate it and pass it on to you. He knows what flavor he allocated, and he is responsible to free it whenever he is done using it.
How does the caller know how much to allocate? Like many Windows APIs have your function return the required amount of bytes when called e.g. with a NULL pointer, then do the job when provided with a non-NULL pointer (using IsBadWritePtr if it is suitable for your case to double-check accessibility).
This can also be much much more efficient. Memory allocations COST a lot. Too many memory allocations cause heap fragmentation and then the allocations cost even more. That's why in kernel mode we use the so called "look-aside lists". To minimize the number of memory allocations done, we reuse the blocks we have already allocated and "freed", using services that the NT Kernel provides to driver writers. If you pass on the responsibility for memory allocation to your caller, then he might be passing you cheap memory from the stack (_alloca), or passing you the same memory over and over again without any additional allocations. You don't care of course, but you DO allow your caller to be in charge of optimal memory handling.
To elaborate on the use of the NULL terminator in C: You cannot allocate a "C string" you can allocate a char array and store a string in it, but malloc and free just see it as an array of the requested length.
A C string is not a data type but a convention for using a char array where the null character '\0' is treated as the string terminator. This is a way to pass strings around without having to pass a length value as a separate argument. Some other programming languages have explicit string types that store a length along with the character data to allow passing strings in a single parameter.
Functions that document their arguments as "C strings" are passed char arrays but have no way of knowing how big the array is without the null terminator so if it is not there things will go horribly wrong.
You will notice functions that expect char arrays that are not necessarily treated as strings will always require a buffer length parameter to be passed. For example if you want to process char data where a zero byte is a valid value you can't use '\0' as a terminator character.
You could do what some of the MS Windows APIs do where you (the caller) pass a pointer and the size of the memory you allocated. If the size isn't enough, you're told how many bytes to allocate. If it was enough, the memory is used and the result is the number of bytes used.
Thus the decision about how to efficiently use memory is left to the caller. They can allocate a fixed 255 bytes (common when working with paths in Windows) and use the result from the function call to know whether more bytes are needed (not the case with paths due to MAX_PATH being 255 without bypassing Win32 API) or whether most of the bytes can be ignored... The caller could also pass zero as the memory size and be told exactly how much needs to be allocated - not as efficient processing-wise, but could be more efficient space-wise.
You can certainly preallocate to an upperbound, and use all or something less. Just make sure you actually use all or something less.
Making two passes is also fine.
You asked the right questions about the tradeoffs.
How do you decide?
Use two passes, initially, because:
1. you'll know you aren't wasting memory.
2. you're going to profile to find out where
you need to optimize for speed anyway.
3. upperbounds are hard to get right before
you've written and tested and modified and
used and updated the code in response to new
requirements for a while.
4. simplest thing that could possibly work.
You might tighten up the code a little, too. Shorter is usually better. And the more the code takes advantage of known truths, the more comfortable I am that it does what it says.
char* copyWithoutDuplicateChains(const char* str)
{
if (str == NULL) return NULL;
const char* s = str;
char prev = *s; // [prev][s+1]...
unsigned int outlen = 1; // first character counted
// Determine length necessary by mimicking processing
while (*s)
{ while (*++s == prev); // skip duplicates
++outlen; // new character encountered
prev = *s; // restart chain
}
// Construct output
char* outstr = (char*)malloc(outlen);
s = str;
*outstr++ = *s; // first character copied
while (*s)
{ while (*++s == prev); // skip duplicates
*outstr++ = *s; // copy new character
}
// done
return outstr;
}
참고URL : https://stackoverflow.com/questions/10170808/what-happens-to-memory-after-0-in-a-c-string
'Program Club' 카테고리의 다른 글
| SyncRoot 패턴의 용도는 무엇입니까? (0) | 2020.11.20 |
|---|---|
| SQL에서 max (count (*))를 할 수 있습니까? (0) | 2020.11.20 |
| SQL 대 noSQL (속도) (0) | 2020.11.20 |
| node-gyp 빌드 오류 창 x64 (0) | 2020.11.20 |
| SpringData JPA는 네이티브 쿼리 결과를 Non-Entity POJO에 매핑합니다. (0) | 2020.11.20 |