시퀀스 포인트 간에 휘발성 변수를 여러 번 읽을 수 있습니까?
나는 C에 대해 가능한 많은 세부 사항을 배우기 위해 나만의 C 컴파일러를 만들고 있습니다.지금은 정확히 어떤 방법으로volatile
사물은 통합니다.
혼란스러운 것은 코드의 모든 읽기 액세스가 엄격하게 실행되어야 한다는 것입니다(C11, 6.7.3p7).
휘발성 한정된 유형을 가진 개체는 구현에 알려지지 않은 방식으로 수정되거나 기타 알려지지 않은 부작용이 있을 수 있습니다.따라서 이러한 대상을 지칭하는 표현은 5.1.2.3에 설명된 바와 같이 추상 기계의 규칙에 따라 엄격하게 평가되어야 합니다.또한 모든 시퀀스 시점에서 객체에 마지막으로 저장된 값은 앞서 언급한 알려지지 않은 요인에 의해 수정된 것을 제외하고는 추상 기계에 의해 규정된 값과 일치해야 합니다.134) 휘발성 적격 유형을 가진 객체에 대한 접근을 구성하는 것이 구현 정의됩니다.
: ina = volatile_var - volatile_var;
, 휘발성 변수는 두 번 읽어야 하며 따라서 컴파일러는 최적화할 수 없습니다.a = 0;
동시에 시퀀스 포인트 간의 평가 순서는 미정입니다(C11, 6.5p3).
연산자와 피연산자의 그룹화는 구문으로 표시됩니다.나중에 지정하는 것을 제외하고 하위 식의 부작용 및 값 계산은 시퀀싱되지 않습니다.
: inb = (c + d) - (e + f)
추가사항을 평가하는 순서는 순서가 정해지지 않았기 때문에 지정되지 않습니다.
그러나 이 평가가 부작용을 발생시키는 순서가 없는 개체에 대한 평가는 다음과 같습니다.volatile
예를 들어, 동작이 정의되지 않았습니다(C11, 6.5p2).
스칼라 객체에 대한 부작용이 동일한 스칼라 객체에 대한 다른 부작용 또는 동일한 스칼라 객체의 값을 사용한 값 계산에 대해 시퀀싱되지 않은 경우 동작은 정의되지 않습니다.식의 하위 식에 허용 가능한 순서가 여러 개 있는 경우 순서에 따라 그러한 순서 없는 부작용이 발생하면 동작이 정의되지 않습니다.
이것은 다음과 같은 표현을 의미합니까?x = volatile_var - (volatile_var + volatile_var)
정의되지 않습니까?이런 경우 컴파일러가 경고를 던져야 합니까?
저는 CLANG과 GCC가 무엇을 하는지 알아보려고 했습니다.오류도 경고도 아닙니다.출력된 asm은 아래 asm mrisc-vasm에 표시된 것처럼 변수가 실행 순서로 읽히지 않고 왼쪽에서 오른쪽으로 읽혀지는 것을 나타냅니다.
const int volatile thingy = 0;
int main()
{
int new_thing = thingy - (thingy + thingy);
return new_thing;
}
main:
lui a4,%hi(thingy)
lw a0,%lo(thingy)(a4)
lw a5,%lo(thingy)(a4)
lw a4,%lo(thingy)(a4)
add a5,a5,a4
sub a0,a0,a5
ret
편집 : "왜 컴파일러들이 받아들이냐"는 질문이 아니라 "우리가 C11 표준을 엄격하게 따르면 정의되지 않은 행동인가"를 묻는 것입니다.표준은 그것이 정의되지 않은 행동이라고 명시하는 것처럼 보이지만, 나는 그것에 대해 정확하게 해석하기 위해 그것에 대한 더 많은 정밀도가 필요합니다.
(ISO 9899:2018) 표준을 문자 그대로 읽어보면, 정의되지 않은 행동입니다.
C175.1.2.3/2 - 부작용의 정의:
액세스( a)
volatile
객체, 객체 수정, 파일 수정 또는 이러한 작업을 수행하는 함수를 호출하는 것은 모두 부작용입니다.
C17 6.5/2 - 피연산자 순서:
스칼라 객체에 대한 부작용이 동일한 스칼라 객체에 대한 다른 부작용 또는 동일한 스칼라 객체의 값을 사용한 값 계산에 대해 시퀀싱되지 않은 경우 동작은 정의되지 않습니다.식의 하위 식에 허용 가능한 순서가 여러 개 있는 경우 순서에 따라 그러한 순서 없는 부작용이 발생하면 동작이 정의되지 않습니다.
그래서 말 그대로 표준을 읽을 때,volatile_var - volatile_var
확실히 정의되지 않은 행동입니다.인용된 두 문장이 모두 적용되기 때문에 실제로 UB를 두 번 연속해서 사용합니다.
이 텍스트는 C11에서 꽤 많이 변했다는 점도 참고하시기 바랍니다.이전에 C99는 6.5/2:
이전 시퀀스 포인트와 다음 시퀀스 포인트 사이에 객체는 식의 평가를 통해 최대 한 번까지 저장된 값을 수정해야 합니다.또한 저장할 값을 결정하기 위해서만 사전 값을 읽어야 합니다.
즉, 동작은 이전에 C99(불특정 평가 순서)에서 지정되지 않았지만 C11의 변경 사항에 의해 정의되지 않았습니다.
그렇기는 하지만, 컴파일러는 원하는 대로 평가의 순서를 다시 매기는 것 외에, 주어진 조건에서 최적화할 수 있는 것이 별로 없기 때문에 이 표현으로 거칠고 미친 짓을 할 이유가 없습니다.volatile
.
구현의 품질로서 메인스트림 컴파일러는 C99의 이전 "지정되지 않은" 동작을 유지하는 것으로 보입니다.
C11에 의하면, 이것은 정의되지 않은 행동입니다.
5.1.2.3 프로그램 실행, 제2항(볼딩 마인):
휘발성 객체에 접근하거나, 객체를 수정하거나, 파일을 수정하거나, 이러한 작업을 수행하는 함수를 호출하는 것은 모두 부작용입니다.
그리고 6.5 표현, 2항(다시, 내 것을 굵게 하는 것):
스칼라 객체에 대한 부작용이 동일한 스칼라 객체에 대한 다른 부작용 또는 동일한 스칼라 객체의 값을 사용한 값 계산에 대해 시퀀싱되지 않은 경우 동작은 정의되지 않습니다.
이 컴파일러는 컴파일러이므로 원하는 동작을 자유롭게 정의할 수 있습니다.
다른 답변들이 지적한 바와 같이, A에 접속하는 것은volatile
-적격 변수는 부작용이며, 부작용은 흥미롭고, 시퀀스 포인트 간에 다중 부작용이 있는 것은 특히 흥미롭고, 시퀀스 포인트 간에 동일한 객체에 영향을 미치는 다중 부작용이 있는 것은 정의되지 않습니다.
정의되지 않은 방법/이유에 대한 예로, 입력 스트림에서 2바이트의 빅 엔디언 값을 읽는 (잘못된) 코드를 생각해 보십시오.ifs
:
uint16_t val = (getc(ifs) << 8) | getc(ifs); /* WRONG */
이 코드는 (빅 엔디안을 구현하기 위해) 둘이getc
호출은 왼쪽에서 오른쪽으로 순서대로 수행되지만 물론 이는 전혀 보장되지 않으므로 이 코드가 잘못된 이유입니다.
중는, 입니다.volatile
한정자는 is 입력 레지스터에 대한 것입니다.그래서 만약 당신에게 휘발성 변수가 있다면
volatile uint8_t inputreg;
읽을 때마다 어떤 장치에 다음 바이트가 들어오는 경우, 즉 단순히 변수에 접근하는 경우에만 해당됩니다.inputreg
부르는 것과 같습니다.getc()
스트림에 이 코드를 작성할 수 있습니다.
uint16_t val = (inputreg << 8) | inputreg; /* ALSO WRONG */
그리고 그것은 그들이 그들을 도와주는 것과 거의 똑같이 틀렸습니다.getc()
위의 코드
본 표준에는 "정의되지 않은 동작"보다 더 구체적인 용어는 없습니다. 일부 구현 또는 대부분의 구현에 대해 명확하게 정의되어야 하는 동작을 설명할 수 있지만, 구현 정의 기준에 따라 다른 구현에 대해 예측 불가능하게 동작할 수도 있습니다.오히려, 이 표준의 작성자들은 그러한 행동에 대해 아무 말도 하지 않기 위해 비상한 노력을 기울입니다.
이 용어는 잠재적으로 유용한 최적화가 어떤 경우에는 프로그램 동작에 눈에 띄게 영향을 미칠 수 있는 상황에 대한 캐치올(catch-all)로도 사용되며, 이러한 최적화가 정의된 상황에서 프로그램 동작에 영향을 미치지 않도록 보장합니다.
이 표준은 휘발성 자격 액세스의 의미론을 "구현 정의된 구현"이라고 명시하며, 특정 종류의 최적화가 다음을 포함하는 플랫폼이 있습니다.volatile
- 시퀀스 포인트 간에 둘 이상의 액세스가 발생할 경우 정규화된 액세스를 관찰할 수 있습니다.간단한 예로, 일부 플랫폼에는 읽기-수정-쓰기 작업이 있으며, 그 의미론은 이산 읽기, 수정 및 쓰기 작업과 눈에 띄게 구별될 수 있습니다.프로그래머가 다음과 같이 쓸 경우:
void x(int volatile *dest, int volatile *src)
{
*dest = *src | 1;
}
그리고 두 개의 포인터가 동일했습니다. 그러한 함수의 동작은 컴파일러가 포인터가 동일하다는 것을 인식하고 이산 읽기 및 쓰기 작업을 결합된 읽기-modify-쓰기로 대체했는지 여부에 달려 있습니다.
확실히 이러한 구분은 대부분의 경우 중요하지 않을 것이며, 개체를 두 번 읽는 경우에는 특히 중요하지 않을 것입니다.그럼에도 불구하고, 이 표준은 그러한 최적화가 실제로 프로그램 동작에 영향을 미치는 상황을 그러한 최적화의 효과를 감지하는 것이 불가능한 상황과 구별하려는 시도를 하지 않습니다."non-portable or errors"라는 문구에서 non-portable이지만 대상 플랫폼에서는 정확한 구문을 제외한다는 개념은 읽기-수정-쓰기 병합과 같은 컴파일러 최적화가 "정확한" 프로그램에서는 전혀 쓸모가 없다는 흥미로운 아이러니를 초래할 것입니다.
특별히 언급된 경우를 제외하고는 Undefined Behavior(정의되지 않은 동작)이 있는 프로그램에서는 진단이 필요하지 않습니다.그러니 이 코드를 받아들이는 것이 잘못된 것은 아닙니다.
일반적으로 시퀀스 포인트 간에 동일한 휘발성 스토리지가 여러 번 액세스되는지 여부를 알 수 없습니다(2개를 사용하는 함수를 고려하십시오).volatile int*
매개 변수, 없음restrict
, 분석이 불가능한 가장 단순한 예로서).
즉, 문제가 있는 상황을 감지할 수 있을 때 사용자가 도움이 될 수 있으므로 진단 결과를 도출하는 작업을 권장합니다.
IMO 그것은 합법적이지만 매우 나쁩니다.
int new_thing = thingy - (thingy + thingy);
다중이용volatile
하나의 식에 변수가 허용되며 경고가 필요하지 않습니다.하지만 프로그래머의 관점에서 보면 매우 나쁜 코드 라인입니다.
이것은 x = volatile_var - (volatile_var + volatile_var)와 같은 표현이 정의되지 않았다는 것을 의미합니까?이런 일이 발생하면 컴파일러에서 오류가 발생해야 합니까?
아니요, C standard는 그 읽기들이 어떻게 주문되어야 하는지에 대해 아무것도 언급하지 않습니다.그것은 실행에 맡겨져 있습니다.제가 알고 있는 모든 구현은 이 예와 같이 가장 쉽게 구현할 수 있습니다. https://godbolt.org/z/99498141d
언급URL : https://stackoverflow.com/questions/75247233/can-volatile-variables-be-read-multiple-times-between-sequence-points
'programing' 카테고리의 다른 글
PSC Credential을 파일에 저장합니다. (0) | 2023.10.20 |
---|---|
구조물을 직접 할당할 수 없는 이유는 무엇입니까? (0) | 2023.10.20 |
레프트 아우터가 라라벨과 합류하는 방법은? (0) | 2023.10.20 |
jQuery Validation Plugin을 사용하여 양식이 프로그래밍적으로 유효한지 확인하는 방법 (0) | 2023.10.20 |
스파크 데이터 프레임에서 전체 열 내용을 표시하는 방법은 무엇입니까? (0) | 2023.10.20 |