Programing

왜 일부 플랫폼에서는 그렇지 않고 다른 플랫폼에서는 루프 종료에 사용합니까?

lottogame 2020. 4. 10. 08:04
반응형

왜 일부 플랫폼에서는 그렇지 않고 다른 플랫폼에서는 루프 종료에 사용합니까?


나는 최근에 C를 배우기 시작했고 C를 ​​주제로 수업을 들었습니다. 나는 현재 루프로 놀고 있으며 설명하는 방법을 모르는 이상한 행동을하고 있습니다.

#include <stdio.h>

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%d \n", sizeof(array)/sizeof(int));
  return 0;
}

Ubuntu 14.04를 실행하는 랩톱에서이 코드는 깨지지 않습니다. 완료됩니다. CentOS 6.6을 운영하는 학교 컴퓨터에서도 잘 작동합니다. Windows 8.1에서 루프는 종료되지 않습니다.

더 이상한 점은 for루프 조건을 다음과 같이 편집 할 때 i <= 11코드가 Ubuntu를 실행하는 랩톱에서만 종료 된다는 것 입니다. CentOS 및 Windows에서는 종료되지 않습니다.

누구든지 메모리에서 무슨 일이 일어나고 있으며 동일한 코드를 실행하는 다른 OS가 다른 결과를 낼 수 있는지 설명 할 수 있습니까?

편집 : for 루프가 범위를 벗어남을 알고 있습니다. 의도적으로하고 있습니다. OS와 컴퓨터마다 동작이 어떻게 다른지 알 수 없습니다.


Ubuntu 14.04를 실행하는 랩톱 에서이 코드는 실행을 중단하지 않습니다. CentOS 6.6을 운영하는 학교 컴퓨터에서도 잘 작동합니다. Windows 8.1에서 루프는 종료되지 않습니다.

더 이상한 점은 for루프 조건을 다음과 같이 편집하면 i <= 11Ubuntu를 실행하는 랩톱에서만 코드가 종료됩니다. CentOS와 Windows는 종료되지 않습니다.

방금 메모리 스톰 핑을 발견했습니다. 여기에서 더 많은 내용을 읽을 수 있습니다 : “메모리 스톰프”란 무엇입니까?

당신이 할당 할 때 int array[10],i;, 그 변수들은 메모리로 들어갑니다 (특히, 그것들은 함수와 관련된 메모리 블록 인 스택에 할당됩니다). array[]그리고 i메모리에서 서로 아마 인접. Windows 8.1에서는 i에 있습니다 array[10]. CentOS에서는 i에 있습니다 array[11]. 그리고 우분투에서는 어느 곳에도 없습니다 (아마도 array[-1]?에 있습니다).

이 디버깅 문을 코드에 추가하십시오. 반복 10 또는 11에서을 array[i]가리 킵니다 i.

#include <stdio.h>

int main() 
{ 
  int array[10],i; 

  printf ("array: %p, &i: %p\n", array, &i); 
  printf ("i is offset %d from array\n", &i - array);

  for (i = 0; i <=11 ; i++) 
  { 
    printf ("%d: Writing 0 to address %p\n", i, &array[i]); 
    array[i]=0; /*code should never terminate*/ 
  } 
  return 0; 
} 

버그는 다음 코드들 사이에 있습니다.

int array[10],i;

for (i = 0; i <=10 ; i++)

array[i]=0;

array10 개의 요소 만 있기 때문에 마지막 반복 array[10] = 0;에서 버퍼 오버 플로우가 발생합니다. 버퍼 오버 플로우는 UNDEFINED BEHAVIOR 입니다. 즉, 하드 드라이브를 포맷하거나 악마가 코에서 날아갈 수 있습니다.

모든 스택 변수가 서로 인접하여 배치되는 것이 일반적입니다. 쓰기 i위치에 있으면 array[10]UB가로 재설정 i되어 0종료되지 않은 루프가 발생합니다.

수정하려면 루프 조건을로 변경하십시오 i < 10.


루프의 마지막 실행은 무엇입니까? array[10]에 쓰지만 배열에는 0에서 9까지 번호가 지정된 10 개의 요소 만 있습니다. C 언어 사양에서는 이것이 "정의되지 않은 동작"이라고 말합니다. 이것이 실제로 의미하는 것은 프로그램이 int메모리 바로 뒤에 array있는 크기의 메모리 에 쓰려고 시도한다는 것 입니다. 그러면 실제로 발생하는 내용에 따라 달라지며 운영 체제뿐만 아니라 컴파일러, 컴파일러 옵션 (예 : 최적화 설정), 프로세서 아키텍처, 주변 코드에 따라 달라집니다. 주소 공간 랜덤 화 (아마도이 ​​장난감 예제는 아니지만 실제 상황에서는 발생) 로 인해 실행에 따라 달라질 수 있습니다 . 몇 가지 가능성은 다음과 같습니다.

  • 위치가 사용되지 않았습니다. 루프가 정상적으로 종료됩니다.
  • 위치는 값이 0 인 무언가에 사용되었습니다. 루프가 정상적으로 종료됩니다.
  • 위치에는 함수의 반환 주소가 포함되어 있습니다. 루프는 정상적으로 종료되지만 프로그램은 주소 0으로 점프하려고하기 때문에 충돌합니다.
  • 위치는 변수를 포함합니다 i. i0에서 다시 시작 하기 때문에 루프가 종료되지 않습니다 .
  • 위치에 다른 변수가 있습니다. 루프는 정상적으로 종료되지만 "흥미로운"일이 발생합니다.
  • 위치가 유효하지 않은 메모리 주소입니다. 예를 들어 array가상 메모리 페이지의 끝에 있고 다음 페이지가 매핑되지 않기 때문입니다.
  • 악마는 코에서 날아갑니다 . 다행히도 대부분의 컴퓨터에는 필수 하드웨어가 없습니다.

Windows에서 관찰 한 것은 컴파일러가 변수 i를 메모리 바로 다음 에 배치하기로 결정 했기 때문에에 array[10] = 0할당했습니다 i. 우분투와 CentOS에서는 컴파일러가 설치되지 않았습니다 i. 거의 모든 C 구현은 하나의 주요 예외를 제외 하고 메모리의 로컬 변수를 메모리 스택으로 그룹화합니다 . 일부 로컬 변수는 레지스터 에 완전히 배치 될 수 있습니다 . 변수가 스택에 있더라도 변수의 순서는 컴파일러에 의해 결정되며 소스 파일의 순서뿐만 아니라 유형에 따라 달라질 수 있습니다 (구멍을 남길 수있는 정렬 제약 조건에 메모리 낭비를 피하기 위해) , 컴파일러 이름, 컴파일러 내부 데이터 구조 등에 사용되는 일부 해시 값

컴파일러에서 수행하기로 결정한 작업을 찾으려면 어셈블러 코드를 보여 주도록 지시 할 수 있습니다. 아, 그리고 어셈블러를 해독하는 법을 배우십시오 (작성하는 것보다 쉽습니다). GCC (및 특히 유닉스 세계의 다른 컴파일러) -S를 사용하면 바이너리 대신 어셈블러 코드를 생성 하는 옵션 전달하십시오 . 예를 들어, 다음은 최적화 옵션 -O0(최적화 없음)을 사용하여 amd64에서 GCC로 컴파일하는 루프에 대한 어셈블러 스 니펫 이며 주석은 수동으로 추가됩니다.

.L3:
    movl    -52(%rbp), %eax           ; load i to register eax
    cltq
    movl    $0, -48(%rbp,%rax,4)      ; set array[i] to 0
    movl    $.LC0, %edi
    call    puts                      ; printf of a constant string was optimized to puts
    addl    $1, -52(%rbp)             ; add 1 to i
.L2:
    cmpl    $10, -52(%rbp)            ; compare i to 10
    jle     .L3

여기서 변수 i는 스택 상단에서 52 바이트 아래에있는 반면 배열은 스택 상단에서 48 바이트 아래에서 시작합니다. 따라서이 컴파일러 i는 배열 바로 앞에 위치 합니다. 에 쓰면 덮어 쓰게 i됩니다 array[-1]. 로 변경 array[i]=0하면 array[9-i]=0이러한 특정 컴파일러 옵션을 사용하여이 특정 플랫폼에서 무한 루프를 얻게됩니다.

이제로 프로그램을 컴파일하자 gcc -O1.

    movl    $11, %ebx
.L3:
    movl    $.LC0, %edi
    call    puts
    subl    $1, %ebx
    jne     .L3

더 짧아요! 컴파일러는 스택 위치 할당을 거부했을 뿐 아니라 i레지스터에 저장되어있을 ebx뿐 아니라 array요소를 설정하기 위한 메모리를 할당 하거나 요소를 설정하기위한 코드를 생성하지 않았습니다. 이제까지 사용됩니다.

이 예제를보다 잘 설명하기 위해 컴파일러에서 최적화 할 수없는 것을 제공하여 배열 할당이 수행되도록합니다. 별도의 컴파일, 컴파일러가 (이 링크시 최적화 된 않는 다른 파일에 어떻게되는지 모르기 때문에 - 그 작업을 쉽게 수행하는 방법은 다른 파일에서 배열을 사용하는 것 gcc -O0또는 gcc -O1하지 않습니다). 다음을 포함하는 소스 파일 use_array.c만듭니다.

void use_array(int *array) {}

소스 코드를

#include <stdio.h>
void use_array(int *array);

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%zd \n", sizeof(array)/sizeof(int));
  use_array(array);
  return 0;
}

와 컴파일

gcc -c use_array.c
gcc -O1 -S -o with_use_array1.c with_use_array.c use_array.o

이번에는 어셈블러 코드가 다음과 같습니다.

    movq    %rsp, %rbx
    leaq    44(%rsp), %rbp
.L3:
    movl    $0, (%rbx)
    movl    $.LC0, %edi
    call    puts
    addq    $4, %rbx
    cmpq    %rbp, %rbx
    jne     .L3

이제 배열은 스택에서 위에 44 바이트입니다. 무엇에 대해 i? 어디에도 나타나지 않습니다! 그러나 루프 카운터는 레지스터에 유지됩니다 rbx. 정확히는 i아니지만의 주소입니다 array[i]. 컴파일러는의 값을 i직접 사용하지 않았기 때문에 루프를 실행할 때마다 0을 저장할 위치를 계산하기 위해 산술을 수행 할 필요가 없다고 결정했습니다 . 그 주소는 루프 변수이며 경계를 결정하는 산술은 컴파일 타임 (배열 요소 당 11 반복에 4 바이트를 곱하여 44를 얻음)과 부분적으로 런타임에 한 번 또는 루프가 시작되기 전에 수행되었습니다 ( 빼기를 수행하여 초기 값을 얻습니다).

이 매우 간단한 예에서도 컴파일러 옵션 변경 (최적화 켜기) 또는 사소한 변경 ( array[i]~ array[9-i]) 또는 관련이없는 것으로 변경 (호출 추가 use_array)이 실행 프로그램이 생성하는 것과 큰 차이를 만드는 방법을 보았습니다. 컴파일러에 의해 수행됩니다. 컴파일러 최적화는 정의되지 않은 동작을 호출하는 프로그램에서 직관적이지 않은 것처럼 보일 수있는 많은 작업을 수행 할 수 있습니다 . 이것이 정의되지 않은 행동이 완전히 정의되지 않은 이유입니다. 실제 프로그램에서 트랙에서 약간 벗어나면 숙련 된 프로그래머조차도 코드의 기능과 수행해야 할 작업 간의 관계를 이해하기가 매우 어려울 수 있습니다.


Java와 달리 C는 배열 경계 검사를 수행하지 않습니다. 즉, ArrayIndexOutOfBoundsException배열 인덱스가 유효한지 확인하는 작업은 프로그래머에게 맡겨집니다. 이 작업을 의도적으로 수행하면 정의되지 않은 동작이 발생하여 어떤 일이 발생할 수 있습니다.


배열의 경우 :

int array[10]

인덱스는 0~ 범위에서만 유효 합니다 9. 그러나 다음을 시도하고 있습니다.

for (i = 0; i <=10 ; i++)

array[10]여기에 액세스 하여 조건을i < 10


경계 위반이 있으며 비 종료 플랫폼에서는 i루프가 끝날 때 실수 로 0으로 설정 되어 다시 시작되도록 믿습니다 .

array[10]유효하지 않다; 그것은 10 개 요소를 포함 array[0]통해 array[9], 그리고 array[10]11이다. 루프는 다음과 같이 전에 중지되도록 작성해야 10합니다.

for (i = 0; i < 10; i++)

어디 array[10]땅은 구현 정의하고, 재미있게, 당신의 플랫폼이에, 그것은에 토지 i이러한 플랫폼은 분명히 바로 뒤에 배치하는 array. i는 0으로 설정되고 루프는 영원히 계속됩니다. 다른 플랫폼의 경우, i이전 array위치 하거나 array이후에 패딩이있을 수 있습니다.


당신 은 인덱스 가지고 int array[10]있음 array선언 0합니다 9( 10보유 할 수있는 정수 요소). 그러나 다음 루프는

for (i = 0; i <=10 ; i++)

루프 시간 010의미 11합니다. 따라서 i = 10버퍼가 오버플로되고 정의되지 않은 동작이 발생하는시기 입니다.

따라서 이것을 시도하십시오 :

for (i = 0; i < 10 ; i++)

또는,

for (i = 0; i <= 9 ; i++)

에 정의되어 있지 array[10]않으며 앞에서 설명한대로 정의되지 않은 동작제공합니다 . 다음과 같이 생각하십시오.

식료품 카트에 10 개의 품목이 있습니다. 그들은:

0 : 시리얼
1 상자 : 빵
2 : 우유
3 : 파이
4 : 계란
5 : 케이크
6 : 2 리터의 소다
7 : 샐러드
8 : 버거
9 : 아이스크림

cart[10]정의되지 않았으며 일부 컴파일러에서 범위를 벗어난 예외가 발생할 수 있습니다. 그러나 많은 것은 분명히 그렇지 않습니다. 명백한 11 번째 항목은 실제로 장바구니에 없는 항목입니다 . 11 번째 항목은 내가 "폴더리스트 항목"이라고 부릅니다. 그것은 존재하지 않았지만 거기에있었습니다.

어떤 컴파일러가 제공하는 이유 i의 인덱스를 array[10]하거나 array[11]또는 array[-1]때문에 당신의 초기화 / 선언문이다. 일부 컴파일러는 이것을 다음과 같이 해석합니다.

  • "10 개 블록 할당 int에 대한들 array[10]과 다른 int. 블록 , 쉽게하기 위해 서로 바로 옆에 넣어."
  • 이전과 동일하지만 공백을 한두 칸 이동하면을 array[10]가리 키지 않습니다 i.
  • 이전과 동일한 작업을 수행하지만, 할당 i에서 array[-1], 또는 OS가 처리 할 수 있기 때문에 완전히 다른 지점에이를 할당하고 그것의 (배열의 인덱스는 할 수없는, 또는 음수가 될 수 없습니다해야하기 때문에) 안전합니다.

일부 컴파일러는 작업이 더 빨라지기를 원하고 일부 컴파일러는 안전을 선호합니다. 상황에 관한 모든 것입니다. 예를 들어 고대 BREW OS (기본 전화의 OS) 용 앱을 개발하는 경우 안전에 신경 쓰지 않습니다. iPhone 6을 개발 중이라면 무엇이든 빠르게 실행할 수 있으므로 안전에 중점을 둘 필요가 있습니다. (Apple의 App Store 지침을 읽거나 Swift 및 Swift 2.0 개발에 대해 읽어 보셨습니까?)


크기가 10 인 배열을 만들었으므로 for 루프 조건은 다음과 같아야합니다.

int array[10],i;

for (i = 0; i <10 ; i++)
{

현재 메모리를 사용하여 할당되지 않은 위치에 액세스하려고 array[10]하는데 정의되지 않은 동작 이 발생하고 있습니다 . 정의되지 않은 동작은 프로그램이 결정되지 않은 방식으로 작동하므로 각 실행마다 다른 출력을 줄 수 있습니다.


글쎄, C 컴파일러는 전통적으로 경계를 확인하지 않습니다. 프로세스에 "포함되지 않은"위치를 참조 할 경우 분할 오류가 발생할 수 있습니다. 그러나 로컬 변수는 스택에 할당되며 메모리가 할당되는 방식에 따라 배열 바로 뒤에있는 영역 ( array[10])이 프로세스의 메모리 세그먼트에 속할 수 있습니다. 따라서 세그먼테이션 결함 트랩이 발생하지 않으며 이것이 바로 사용자가 경험하는 것입니다. 다른 사람들이 지적했듯이 이것은 C에서 정의되지 않은 동작이며 코드가 불규칙한 것으로 간주 될 수 있습니다. C를 배우고 있으므로 코드에서 경계를 확인하는 습관을들이는 것이 좋습니다.


a[10]실제로 덮어 쓰기를 시도하기 위해 메모리가 배치 될 수있는 가능성을 넘어서, i최적화 컴파일러는 i코드에 처음 액세스하지 않은 상태에서 루프 테스트에 10보다 큰 값으로 도달 할 수 없다고 결정할 수도 있습니다. 존재하지 않는 배열 요소 a[10].

해당 요소에 액세스하려는 시도는 정의되지 않은 동작이므로 컴파일러는 해당 시점 이후에 프로그램이 수행 할 수있는 작업에 대한 의무가 없습니다. 보다 구체적으로, 컴파일러는 루프 인덱스가 10보다 클 경우 루프 인덱스를 검사하기위한 코드를 생성 할 의무가 없으므로,이를 검사하기 위해 코드를 생성 할 의무는 없습니다. 대신 <=10테스트가 항상 참 이라고 가정 할 수 있습니다. 코드를 a[10]작성하지 않고 읽는 경우에도 마찬가지 입니다.


당신으로 반복은 과거 때 i==9실제로 위치한 '배열 항목'제로 지정 배열 과거 일부 다른 데이터를 overwritnig 것 정도. 아마도 당신 i은 뒤에 위치한 변수 를 덮어 쓸 것입니다 a[]. 당신은 단순히 그런 식으로 리셋 i제로에 변수를 따라서 그리고 루프를 다시 시작합니다.

i루프에서 인쇄하면 스스로 알아낼 수 있습니다 .

      printf("test i=%d\n", i);

그냥 대신

      printf("test \n");

물론 그 결과는 변수에 대한 메모리 할당에 크게 좌우되며, 결과적으로 컴파일러 및 해당 설정에 따라 달라 지므로 일반적으로 정의되지 않은 동작 이므로 다른 시스템 또는 다른 운영 체제 또는 다른 컴파일러의 결과가 다를 수 있습니다.


에러는 부분 배열 [10]에있다. w / c는 또한 i의 주소이다 (int array [10], i;). array [10]이 0으로 설정되면 i는 0입니다. w / c는 전체 루프를 재설정하고 무한 루프를 발생시킵니다. array [10]이 0-10 사이이면 무한 루프가 발생합니다. 올바른 루프는 (i = 0; i <10; i ++) {...} int array [10], i; (i = 0; i <= 10; i ++) 배열 [i] = 0;


나는 위에서 찾을 수없는 것을 제안 할 것이다.

array [i] = 20을 할당 해보십시오;

나는 이것이 어디에서나 코드를 끝내야한다고 생각한다. (i <= 10 또는 ll을 유지한다면)

이것이 실행되면 여기에 지정된 답변이 이미 올바른지 확실하게 결정할 수 있습니다 [예를 들어 메모리를 소모하는 것과 관련된 답변].


여기에 두 가지 잘못된 점이 있습니다. int i는 실제로 스택에서 볼 수있는 배열 요소 array [10]입니다. 인덱싱이 실제로 array [10] = 0을 만들도록 허용했기 때문에 루프 인덱스 i는 절대로 10을 초과하지 않습니다 for(i=0; i<10; i+=1).

i ++는 K & R 이 '나쁜 스타일'이라고 부릅니다. i가 1이 아닌 i의 크기만큼 증가합니다. i ++는 포인터 수학 용이고 i + = 1은 대 수용입니다. 이것은 컴파일러에 따라 다르지만 이식성에 대한 좋은 규칙은 아닙니다.

참고 URL : https://stackoverflow.com/questions/31016660/why-does-this-for-loop-exit-on-some-platforms-and-not-on-others

반응형