programing

C에서 정의되지 않은 동작인가요? 출력을 논리적으로 예측하지 않으면

testmans 2023. 11. 1. 22:13
반응형

C에서 정의되지 않은 동작인가요? 출력을 논리적으로 예측하지 않으면

코드1

#include <stdio.h>
int f(int *a, int b) 
{
  b = b - 1;
  if(b == 0) return 1;
  else {
    *a = *a+1;

    return *a + f(a, b);
  }
}

int main() {
  int X = 5;
  printf("%d\n",f(&X, X));
}

이 C 코드를 생각해 보십시오.여기서 문제는 생산량을 예측하는 것입니다.논리적으로 31개의 출력을 받습니다. (기계의 출력)

반품 문을 다음으로 변경할 때

return f(a, b) + *a;

논리적으로 37을 얻습니다. (기계의 출력)

제 친구 중 한 명이 답례문을 계산하면서 말했습니다.

return *a + f(a, b);

우리는 트리의 깊이로 가는 동안의 값을 계산합니다. 즉, *첫 번째 계산은 다음과 같습니다.f(a, b)라고 불리는 반면에

return f(a,b) + *a;

다시 돌아오는 동안 해결됩니다.f(a, b)그 다음에 먼저 계산됩니다.*a라고 합니다.

이 방법을 사용하여 저는 직접 다음 코드의 출력을 예측해 보았습니다.

코드2

#include <stdio.h>
int foo(int n) 
{
    static int r;
    if(n <= 1)
        return 1;

    r = n + r;
    return r + foo(n - 2);
} 

int main () {
   printf("value : %d",foo(5));
}

위해서return(r+foo(n-2));

enter image description here

출력 논리적으로 14를 받고 있습니다(기계의 출력)

위해서return(foo(n-2)+r);

enter image description here

출력으로 17을 받습니다. (기계 출력)

그러나 시스템에서 코드를 실행하면 두 경우 모두 17이 나옵니다.

내 질문:

  • 제 친구가 제시한 접근 방식이 맞나요?
  • 그렇다면 기계에서 실행할 때 코드 2에서 동일한 출력이 나오는 이유는 무엇입니까?
  • 그렇지 않다면 코드 1코드 2를 정확하게 해석하는 방법은 무엇입니까?
  • C가 참조 통과를 지원하지 않기 때문에 정의되지 않은 동작이 있습니까?코드 1 터프에서 사용되고 있기 때문에 포인터를 사용하여 구현할 수 있습니까?

한마디로 위에서 언급한 4가지 경우의 산출량을 예측하는 정확한 방법을 알고 싶었습니다.

코드1

코드 1의 경우, 왜냐하면 용어들의 평가 순서가return *a + f(a, b);(및 인)return f(a, b) + *a;)는 표준에 의해 지정되지 않으며 함수는 다음 값을 수정합니다.a코드가 특정되지 않은 동작을 하고 있으며 다양한 답변이 가능합니다.

댓글의 분노에서 알 수 있듯이, '정의되지 않은 행동', '지정되지 않은 행동' 등의 용어는 C 표준에서 기술적인 의미를 가지고 있으며, 이 답변의 이전 버전은 '지정되지 않은 행동'을 사용했어야 하는 '정의되지 않은 행동'을 잘못 사용했습니다.

질문의 제목은 "이것이 C에서 정의되지 않은 행동인가?"이며, 답은 "아니요; 그것은 정의되지 않은 행동이 아니라 불특정 행동입니다."입니다.

코드 2 — 수정됨

고정 코드 2의 경우 함수에 지정되지 않은 동작(정적 변수 값)도 있습니다.r는 재귀 호출에 의해 변경되므로 평가 순서를 변경하면 결과가 변경될 수 있습니다.

코드 2 — 사전 개정

코드 2의 경우, 원래 와 같이 표시됩니다.int f(static int n) { … }, 코드가 컴파일되지 않거나(또는 적어도) 해서는 안 됩니다.함수에 대한 인수의 정의에서 허용되는 유일한 저장 클래스는 다음과 같습니다.register, 그래서 의 존재.static컴필레이션 오류를 제공해야 합니다.

ISO/IEC 9899:2011 §6.7.6.3 기능 선언자(원형 포함) 2 파라미터 선언에서 발생해야 하는 유일한 저장 클래스 명세자는register.

macOS 시에라 10.12.2에서 GCC 6.3.0으로 컴파일하는 것은 다음과 같습니다(참고, 추가 경고는 요청하지 않음).

$ gcc -O ub17.c -o ub17
ub17.c:3:27: error: storage class specified for parameter ‘n’
 int foo(static int n)
                    ^

아니요; 그것은 보여진 것처럼 전혀 컴파일되지 않습니다 – 적어도 현대 버전의 GCC를 사용하는 저에게는 그렇지 않습니다.

그러나 함수가 고정되어 있다고 가정할 때 함수는 정의되지 않은 지정되지 않은 동작(정적 변수의 값)도 갖습니다.r는 재귀 호출에 의해 변경되므로 평가 순서를 변경하면 결과가 변경될 수 있습니다.

C 표준은 다음과 같이 말합니다.

6.5.2.2/10 기능 호출:

함수 지정자와 실제 인수 평가 후 실제 통화 전에 시퀀스 포인트가 있습니다.호출된 함수의 본문 실행 전 또는 실행 후에 다른 방식으로 시퀀싱되지 않은 호출 함수(다른 함수 호출 포함)의 모든 평가는 호출된 함수의 실행에 대해 불확정적으로 시퀀싱됩니다1.94)

그리고 각주 86(섹션 6.5/3)은 다음과 같이 말합니다.

프로그램이 실행되는 동안 두 번 이상 평가되는 식에서 하위 식에 대한 순차적이지 않은 및 불확정한 순차적 평가는 서로 다른 평가에서 일관성 있게 수행될 필요가 없습니다.

표현에 있어서return f(a,b) + *a;그리고.return *a + f(a,b);하위 표현에 대한 평가*a불확정적으로 배열되어 있습니다.이 경우 동일한 프로그램에 대해 다른 결과를 볼 수 있습니다.
에 미치는 부작용에 유의하십시오.a위 식에서 순서가 정해지지만 순서는 지정되지 않습니다.


1. 평가 A와 B는 A가 B 이전 또는 이후에 서열화될 때 불확정적으로 서열화되지만 어느 것인지는 미정입니다(C11-5.1.2.3/3).

첫 번째 예시의 정의에 초점을 두겠습니다.

첫 번째 예는 지정되지 않은 동작으로 정의됩니다.이것은 여러 개의 가능한 결과가 있지만 동작이 정의되지 않았다는 것을 의미합니다. (그리고 코드가 이러한 결과를 처리할 수 있다면 동작이 정의됩니다.)

지정되지 않은 동작의 사소한 예는 다음과 같습니다.

int a = 0;
int c = a + a;

왼쪽 a가 먼저 평가될지 오른쪽 a가 먼저 평가될지는 불확실합니다. 왜냐하면 그것들은 순서가 없기 때문입니다.+연산자가 시퀀스 포인트를1 지정하지 않습니다.왼쪽 a를 먼저 평가한 다음 오른쪽 a를 평가하거나 그 반대의 두 가지 순서가 있을 수 있습니다.어느 쪽도 수정되지2 않았으므로 동작이 정의됩니다.


시퀀스 포인트 없이 왼쪽 a 또는 오른쪽 a가 수정된 경우(즉, 시퀀싱되지 않은 경우), 동작은 정의되지2 않습니다.

int a = 0;
int c = ++a + a;


시퀀스 포인트를 사이에 두고 왼쪽 a 또는 오른쪽 a를 수정한 경우 왼쪽과 오른쪽이 불확정하게 순서가3 정해집니다.이것은 순서가 정해졌다는 것을 의미하지만, 어떤 것이 먼저 평가되는지는 지정되지 않습니다.동작이 정의됩니다.쉼표 연산자는 시퀀스 포인트를4 도입합니다.

int a = 0;
int c = a + ((void)0,++a,0);

두 가지 주문이 가능합니다.

왼쪽이 먼저 평가되면 a는 0으로 평가됩니다.그런 다음 오른쪽을 평가합니다.먼저 (void)0을 평가한 후 시퀀스 포인트를 따릅니다.그런 다음 a가 증분되고 그 다음에 시퀀스 포인트가 나타납니다.그러면 0이 0으로 평가되어 왼쪽에 추가됩니다.결과는 0입니다.

오른쪽이 먼저 평가되면 (void)0이 먼저 평가되고 시퀀스 포인트가 이어집니다.그런 다음 a가 증분되고 그 다음에 시퀀스 포인트가 나타납니다.그러면 0은 0으로 평가됩니다.그런 다음 왼쪽을 평가하고 a를 1로 평가합니다.결과는 1.


피연산자가 불확정하게 배열되어 있으므로 예제는 후자의 범주에 속합니다.함수 호출은 위 예제의 쉼표 연산자와 동일한 용도로5 사용됩니다.당신의 예는 복잡해서 당신의 예에도 해당되는 저의 예를 사용하겠습니다.한가지 다른 점이 있다면, 당신의 예에서는 나의 경우보다 더 많은 가능성이 있지만, 추론은 같습니다.

void Function( int* a)
{
    ++(*a);
    return 0;
}
int a = 0;
int c = a + Function( &a );
assert( c == 0 || c == 1 );

두 가지 주문이 가능합니다.

왼쪽이 먼저 평가되면 a는 0으로 평가됩니다.그런 다음 오른쪽을 평가하고 시퀀스 포인트가 있고 함수가 호출됩니다.그런 다음 a가 점증하고 그 다음에 전체6 식의 끝에 의해 도입되는 다른 시퀀스 포인트가 나타나며 그 끝은 세미콜론으로 표시됩니다.그러면 0이 반환되어 0에 추가됩니다.결과는 0입니다.

우변을 먼저 평가하면 시퀀스 포인트가 있고 함수가 호출됩니다.그런 다음 a가 증가하고, 그 다음에 전체 식의 끝에 의해 다른 시퀀스 포인트가 도입됩니다.그러면 0이 반환됩니다.그런 다음 왼쪽을 평가하고 a를 1로 평가하여 0에 추가합니다.결과는 1.


(인용: ISO/IEC 9899:201x)

1 (6.5 식 3)
나중에 지정하는 것을 제외하고 하위 식의 부작용 및 값 계산은 시퀀싱되지 않습니다.

2 (6.5 식 2)
스칼라 객체에 대한 부작용이 동일한 스칼라 객체에 대한 다른 부작용 또는 동일한 스칼라 객체의 값을 사용한 값 계산에 대해 시퀀싱되지 않은 경우 동작은 정의되지 않습니다.

3 (5.1.2.3 프로그램 실행)
평가 A와 B는 A가 B 이전 또는 이후에 순서화될 때 불확정적으로 순서화되지만 어느 것인지는 지정되지 않습니다.

4 (6.5.17 쉼표 조작자 2)
쉼표 연산자의 왼쪽 피연산자를 빈 식으로 평가합니다. 해당 피연산자의 평가와 오른쪽 피연산자의 평가 사이에는 시퀀스 포인트가 있습니다.

5 (6.5.2.2 기능 호출 10)
함수 지정자와 실제 인수 평가 후 실제 통화 전에 시퀀스 포인트가 있습니다.

6 (6.8 문구 및 블럭 4)
완전식의 평가와 평가할 다음 완전식의 평가 사이에는 순서점이 있습니다.

언급URL : https://stackoverflow.com/questions/41775973/is-this-undefined-behaviour-in-c-if-not-predict-the-output-logically

반응형