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));
출력 논리적으로 14를 받고 있습니다(기계의 출력)
위해서return(foo(n-2)+r);
출력으로 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
'programing' 카테고리의 다른 글
MySQL/MariaDB 커넥터를 배포하는 것과 모듈로 추가하는 것의 차이점은 무엇입니까? (0) | 2023.11.01 |
---|---|
백슬래시 탈출 끈을 푸는 방법? (0) | 2023.11.01 |
type cast void 포인터를 사용하는 이유는 무엇입니까? (0) | 2023.11.01 |
ASP.NET 5 / MVC 6 Ajax 모델에서 컨트롤러까지 (0) | 2023.11.01 |
접두사가 없는 targetNamespace 및 xmlns, 차이점은 무엇입니까? (0) | 2023.11.01 |