범위를 벗어난 배열에 액세스하면 오류가 발생하지 않습니다. 왜 그렇습니까?
다음과 같이 C ++ 프로그램에서 범위를 벗어난 값을 할당합니다.
#include <iostream>
using namespace std;
int main()
{
int array[2];
array[0] = 1;
array[1] = 2;
array[3] = 3;
array[4] = 4;
cout << array[3] << endl;
cout << array[4] << endl;
return 0;
}
이 프로그램은 인쇄 3
및 4
. 가능하지 않아야합니다. g ++ 4.3.3을 사용하고 있습니다.
다음은 컴파일 및 실행 명령입니다
$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4
할당 할 때만 array[3000]=3000
세그먼트 오류가 발생합니다.
gcc가 배열 범위를 확인하지 않으면 나중에 심각한 문제가 발생할 수 있으므로 프로그램이 올바른지 어떻게 확인할 수 있습니까?
위의 코드를
vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;
이것도 오류가 없습니다.
모든 C / C ++ 프로그래머의 가장 친한 친구 Undefined Behavior에 오신 것을 환영합니다 .
여러 가지 이유로 언어 표준에 의해 지정되지 않은 것이 많이 있습니다. 이것은 그들 중 하나입니다.
일반적으로 정의되지 않은 동작이 발생할 때마다 어떤 일 이 발생할 수 있습니다. 응용 프로그램이 중단되거나 정지되거나 CD-ROM 드라이브를 꺼내거나 코에서 악마가 나오게 할 수 있습니다. 하드 드라이브를 포맷하거나 모든 포르노를 할머니에게 이메일로 보낼 수 있습니다.
운이 좋지 않으면 제대로 작동하는 것처럼 보일 수도 있습니다 .
이 언어는 단순히 배열 경계 안에 있는 요소에 액세스하면 어떻게되는지 알려줍니다 . 범위를 벗어나면 어떤 일이 발생하는지 정의되지 않은 상태로 남아 있습니다. 그것은 수있는 것 컴파일러에 오늘 작업에하지만 법적 C 또는 C ++하지 않고, 여전히 프로그램을 실행할 때 작동합니다 않는다고 보장 할 수는 없습니다. 아니면 지금도하지 덮어 중요한 데이터를 가지고, 당신은 그냥 것으로, 문제가 발생하지 않은 것으로 되어 원인에가는 - 아직.
에 관해서는 이유를 확인 끝이 없다, 대답에 몇 가지 측면이있다 :
- 배열은 C에서 남은 것입니다. C 배열은 가능한 한 원시적입니다. 연속적인 주소를 가진 일련의 요소들. 단순히 원시 메모리를 노출하기 때문에 경계 검사가 없습니다. C에서 강력한 경계 검사 메커니즘을 구현하는 것은 거의 불가능했을 것입니다.
- C ++에서는 클래스 유형에 대한 경계 검사가 가능합니다. 그러나 배열은 여전히 일반적인 C 호환 가능 배열입니다. 수업이 아닙니다. 또한 C ++은 경계 검사를 비 이상적으로 만드는 또 다른 규칙을 기반으로합니다. C ++ 안내 원칙은 "사용하지 않는 것에 대해서는 비용을 지불하지 않는다"는 것입니다. 코드가 올바른 경우 경계 검사가 필요하지 않으며 런타임 경계 검사의 오버 헤드에 대해 비용을 지불하지 않아도됩니다.
- 따라서 C ++은
std::vector
클래스 템플릿을 제공하므로 둘 다 허용합니다.operator[]
효율적으로 설계되었습니다. 언어 표준에서는 경계 검사를 수행 할 필요가 없습니다 (물론 금지하지는 않지만). 벡터는 또한 보유at()
부재 함수 보장 한계 검사를 수행한다. 따라서 C ++에서는 벡터를 사용하면 두 세계를 모두 활용할 수 있습니다. 당신은 배열과 같은 경계 검사없이 성능을, 그리고 당신이 그것을 할 때 사용 경계 - 검사 액세스 할 수있는 기능을 얻을.
g ++를 사용하여 명령 행 옵션을 추가 할 수 있습니다 -fstack-protector-all
.
귀하의 예에서 다음과 같은 결과가 나타났습니다.
> g++ -o t -fstack-protector-all t.cc
> ./t
3
4
/bin/bash: line 1: 15450 Segmentation fault ./t
실제로 문제를 찾거나 해결하는 데 도움이되지는 않지만 적어도 segfault는 무언가 잘못 되었음을 알려줍니다 .
g ++은 배열 범위를 확인하지 않으며 3, 4로 무언가를 덮어 쓸 수 있지만 실제로는 중요하지 않습니다. 높은 숫자로 시도하면 충돌이 발생합니다.
사용되지 않은 스택의 일부만 덮어 쓰고 있습니다. 스택에 할당 된 공간의 끝에 도달 할 때까지 계속하면 결국 충돌이 발생합니다.
편집 : 당신은 그것을 처리 할 수있는 방법이 없습니다. 정적 코드 분석기는 이러한 오류를 나타낼 수 있지만 너무 간단합니다. 정적 분석기에서도 비슷한 (그러나 더 복잡한) 오류가 감지되지 않을 수 있습니다
내가 아는 한 정의되지 않은 동작입니다. 그것으로 더 큰 프로그램을 실행하면 도중에 충돌 할 것입니다. 바운드 검사는 원시 배열 (또는 std :: vector)의 일부가 아닙니다.
대신 std :: vector을 사용 std::vector::iterator
하면 걱정할 필요가 없습니다.
편집하다:
재미를 위해 이것을 실행하고 충돌 할 때까지 얼마나 걸리는지 확인하십시오.
int main()
{
int array[1];
for (int i = 0; i != 100000; i++)
{
array[i] = i;
}
return 0; //will be lucky to ever reach this
}
편집 2 :
그것을 실행하지 마십시오.
편집 3 :
여기 배열과 포인터와의 관계에 대한 간단한 교훈이 있습니다.
배열 인덱싱을 사용하는 경우 실제로 자동으로 역 참조되는 변장 ( "참조")의 포인터를 사용합니다. 이것이 * (array [1]) 대신 array [1]이 해당 값의 값을 자동으로 반환하는 이유입니다.
다음과 같이 배열에 대한 포인터가있는 경우
int array[5];
int *ptr = array;
그런 다음 두 번째 선언의 "배열"은 실제로 첫 번째 배열에 대한 포인터로 쇠퇴합니다. 이것은 이것과 동등한 동작입니다 :
int *ptr = &array[0];
할당 한 것 이상으로 액세스하려고하면 실제로 다른 메모리 (C ++에서 불평하지 않는)에 대한 포인터를 사용하고 있습니다. 위의 예제 프로그램을 사용하면 다음과 같습니다.
int main()
{
int array[1];
int *ptr = array;
for (int i = 0; i != 100000; i++, ptr++)
{
*ptr++ = i;
}
return 0; //will be lucky to ever reach this
}
프로그래밍에서 종종 다른 프로그램, 특히 운영 체제와 통신해야하기 때문에 컴파일러가 불평하지 않습니다. 이것은 포인터로 꽤 이루어집니다.
힌트
당신이 범위 에러 체크와 빠른 구속 크기의 배열을 가지고 싶다면, 사용하려고 부스트 : : 배열을 (또한, 표준 : TR1 :: 배열 에서 <tr1/array>
그 다음에 C ++ 사양 표준 컨테이너 될 것입니다). std :: vector보다 훨씬 빠릅니다. int array []처럼 힙이나 클래스 인스턴스 내부의 메모리를 예약합니다.
이것은 간단한 샘플 코드입니다.
#include <iostream>
#include <boost/array.hpp>
int main()
{
boost::array<int,2> array;
array.at(0) = 1; // checking index is inside range
array[1] = 2; // no error check, as fast as int array[2];
try
{
// index is inside range
std::cout << "array.at(0) = " << array.at(0) << std::endl;
// index is outside range, throwing exception
std::cout << "array.at(2) = " << array.at(2) << std::endl;
// never comes here
std::cout << "array.at(1) = " << array.at(1) << std::endl;
}
catch(const std::out_of_range& r)
{
std::cout << "Something goes wrong: " << r.what() << std::endl;
}
return 0;
}
이 프로그램은 다음을 인쇄합니다 :
array.at(0) = 1
Something goes wrong: array<>: index out of range
확실히 스택을 덮어 쓰고 있지만 프로그램의 영향은 눈에 띄지 않습니다.
C 또는 C ++는 배열 액세스의 경계를 확인하지 않습니다.
You are allocating the array on the stack. Indexing the array via array[3]
is equivalent to *(array + 3)
, where array is a pointer to &array[0]. This will result in undefined behavior.
One way to catch this sometimes in C is to use a static checker, such as splint. If you run:
splint +bounds array.c
on,
int main(void)
{
int array[1];
array[1] = 1;
return 0;
}
then you will get the warning:
array.c: (in function main) array.c:5:9: Likely out-of-bounds store: array[1] Unable to resolve constraint: requires 0 >= 1 needed to satisfy precondition: requires maxSet(array @ array.c:5:9) >= 1 A memory write may write to an address beyond the allocated buffer.
Run this through Valgrind and you might see an error.
As Falaina pointed out, valgrind does not detect many instances of stack corruption. I just tried the sample under valgrind, and it does indeed report zero errors. However, Valgrind can be instrumental in finding many other types of memory problems, it's just not particularly useful in this case unless you modify your bulid to include the --stack-check option. If you build and run the sample as
g++ --stack-check -W -Wall errorRange.cpp -o errorRange
valgrind ./errorRange
valgrind will report an error.
Undefined behavior working in your favor. Whatever memory you're clobbering apparently isn't holding anything important. Note that C and C++ do not do bounds checking on arrays, so stuff like that isn't going to be caught at compile or run time.
When you initialize the array with int array[2]
, space for 2 integers is allocated; but the identifier array
simply points to the beginning of that space. When you then access array[3]
and array[4]
, the compiler then simply increments that address to point to where those values would be, if the array was long enough; try accessing something like array[42]
without initializing it first, you'll end up getting whatever value happened to already be in memory at that location.
Edit:
More info on pointers/arrays: http://home.netcom.com/~tjensen/ptr/pointers.htm
when you declare int array[2]; you reserve 2 memory spaces of 4 bytes each(32bit program). if you type array[4] in your code it still corresponds to a valid call but only at run time will it throw an unhandled exception. C++ uses manual memory management. This is actually a security flaw that was used for hacking programs
this can help understanding:
int * somepointer;
somepointer[0]=somepointer[5];
As I understand, local variables are allocated on stack, so going out of bounds on your own stack can only overwrite some other local variable, unless you go oob too much and exceed your stack size. Since you have no other variables declared in your function - it does not cause any side effects. Try declaring another variable/array right after your first one and see what will happen with it.
When you write 'array[index]' in C it translates it to machine instructions.
The translation is goes something like:
- 'get the address of array'
- 'get the size of the type of objects array is made up of'
- 'multiply the size of the type by index'
- 'add the result to the address of array'
- 'read what's at the resulting address'
The result addresses something which may, or may not, be part of the array. In exchange for the blazing speed of machine instructions you lose the safety net of the computer checking things for you. If you're meticulous and careful it's not a problem. If you're sloppy or make a mistake you get burnt. Sometimes it might generate an invalid instruction that causes an exception, sometimes not.
A nice approach that i have seen often and I had been used actually is to inject some NULL type element (or a created one, like uint THIS_IS_INFINITY = 82862863263;
) at end of the array.
Then at the loop condition check, TYPE *pagesWords
is some kind of pointer array:
int pagesWordsLength = sizeof(pagesWords) / sizeof(pagesWords[0]);
realloc (pagesWords, sizeof(pagesWords[0]) * (pagesWordsLength + 1);
pagesWords[pagesWordsLength] = MY_NULL;
for (uint i = 0; i < 1000; i++)
{
if (pagesWords[i] == MY_NULL)
{
break;
}
}
This solution won't word if array is filled with struct
types.
As mentioned now in the question using std::vector::at will solve the problem and make a bound check before accessing.
If you need a constant size array that is located on the stack as your first code use the C++11 new container std::array; as vector there is std::array::at function. In fact the function exists in all standard containers in which it have a meaning,i.e, where operator[] is defined :( deque, map, unordered_map) with the exception of std::bitset in which it is called std::bitset::test.
libstdc++, which is part of gcc, has a special debug mode for error checking. It is enabled by compiler flag -D_GLIBCXX_DEBUG
. Among other things it does bounds checking for std::vector
at the cost of performance. Here is online demo with recent version of gcc.
So actually you can do bounds checking with libstdc++ debug mode but you should do it only when testing because it costs notable performance compared to normal libstdc++ mode.
If you change your program slightly:
#include <iostream>
using namespace std;
int main()
{
int array[2];
INT NOTHING;
CHAR FOO[4];
STRCPY(FOO, "BAR");
array[0] = 1;
array[1] = 2;
array[3] = 3;
array[4] = 4;
cout << array[3] << endl;
cout << array[4] << endl;
COUT << FOO << ENDL;
return 0;
}
(Changes in capitals -- put those in lower case if you're going to try this.)
You will see that the variable foo has been trashed. Your code will store values into the nonexistent array[3] and array[4], and be able to properly retrieve them, but the actual storage used will be from foo.
So you can "get away" with exceeding the bounds of the array in your original example, but at the cost of causing damage elsewhere -- damage which may prove to be very hard to diagnose.
As to why there is no automatic bounds checking -- a correctly written program does not need it. Once that has been done, there is no reason to do run-time bounds checking and doing so would just slow down the program. Best to get that all figured out during design and coding.
C++ is based on C, which was designed to be as close to assembly language as possible.
참고URL : https://stackoverflow.com/questions/1239938/accessing-an-array-out-of-bounds-gives-no-error-why
'development' 카테고리의 다른 글
JSON 파일의 몽고 임포트 (0) | 2020.06.09 |
---|---|
파이썬을 사용하여 XML을 JSON으로 변환 하시겠습니까? (0) | 2020.06.09 |
플로팅 액션 버튼 (패브)의 아이콘 크기 조정 (0) | 2020.06.08 |
테이블과 tr 너비로 정렬 가능한 jquery UI (0) | 2020.06.08 |
Android : 알림 표시 줄에서 알림 제거 (0) | 2020.06.08 |