---
title: "식은 순서대로 줄어든다"
description: "컴파일러는 한 줄을 왼쪽부터 읽지 않습니다. 우선순위가 높은 곳부터 값으로 바꿔 하나만 남을 때까지 줄입니다."
author: "유니얼"
pubDate: 2026-09-08
category: "programming/csharp/basics"
tags: ["C#", "기초"]
canonical: https://blog.unialgames.com/posts/csharp-expression-order/
---
앞 글에서 기호를 하나씩 봤습니다. 이제 여러 개가 한 줄에 섞이면 어떻게 되는지 봅니다.

```csharp
int r = 3 + 4 * 5;
```

`23` 입니다. 왼쪽부터 읽었다면 `35` 가 나왔을 겁니다.
**컴파일러는 한 줄을 왼쪽부터 읽지 않습니다.**

> **식은 우선순위가 높은 곳부터 값으로 바뀌고, 값 하나만 남을 때까지 줄어듭니다.**

## 식은 값 하나로 줄어든다

`3 + 4 * 5` 가 계산되는 과정을 한 단계씩 보겠습니다.
**색이 칠해진 부분이 이번 단계에서 계산되는 곳**입니다.

<x-eval>
<script type="application/json">
{
  "label": "식이 줄어드는 순서",
  "cases": [
    {
      "label": "괄호 없이",
      "steps": [
        { "text": "3 + [4 * 5]", "why": "곱셈이 덧셈보다 우선순위가 높습니다. 여기부터 값이 됩니다." },
        { "text": "[3 + 20]", "why": "이제 덧셈만 남았습니다." },
        { "text": "23", "why": "값 하나만 남으면 끝입니다." }
      ],
      "note": "우선순위는 어느 쪽을 먼저 값으로 바꿀지를 정하는 것뿐입니다."
    },
    {
      "label": "괄호를 치면",
      "steps": [
        { "text": "[(3 + 4)] * 5", "why": "괄호는 우선순위를 무시하고 그 안을 먼저 값으로 만듭니다." },
        { "text": "[7 * 5]", "why": "괄호가 사라지고 곱셈만 남았습니다." },
        { "text": "35", "why": "같은 숫자인데 결과가 다릅니다." }
      ],
      "note": "괄호는 새 규칙이 아니라 순서를 직접 정하는 도구입니다."
    },
    {
      "label": "캐스팅이 섞이면",
      "steps": [
        { "text": "(double)([a / b])", "why": "괄호 안이 먼저입니다. a 도 b 도 int 라 정수 나눗셈입니다." },
        { "text": "[(double)3]", "why": "이미 3이 된 값을 실수로 옮깁니다." },
        { "text": "3.0", "why": "없어진 소수는 돌아오지 않습니다. 캐스팅이 늦으면 소용없습니다." }
      ],
      "note": "(double)a / b 는 반대입니다. 캐스팅이 나눗셈보다 먼저라 3.3333... 이 나옵니다."
    }
  ]
}
</script>
</x-eval>

세 번째 것이 앞 글에서 결과가 갈렸던 이유입니다.

```csharp
double r1 = (double)a / b;     // 3.3333...  캐스팅이 먼저
double r2 = (double)(a / b);   // 3          나눗셈이 먼저
```

`(double)(a / b)` 는 **정수로 나눈 뒤에** 실수로 옮기니 이미 늦었습니다.
같은 글자로 되어 있는데 괄호 위치 하나가 결과를 바꿉니다.

## 외우지 말고 괄호를 쓴다

우선순위 표는 이렇습니다. 앞 글에서 본 기호들을 순서대로 놓은 것입니다.

| 순서 | 연산자 |
| --- | --- |
| 1 | `( )` 괄호 |
| 2 | `!` `++` `--` `(타입)` 캐스팅 |
| 3 | `*` `/` `%` |
| 4 | `+` `-` |
| 5 | `<` `>` `<=` `>=` |
| 6 | `==` `!=` |
| 7 | `&&` |
| 8 | `\|\|` |
| 9 | `=` `+=` `-=` 대입 |

외울 필요 없습니다. 알아둘 건 두 가지뿐입니다.

- **산술이 비교보다 먼저고, 비교가 논리보다 먼저고, 대입이 맨 마지막입니다.**
  `if (a + 1 > b && c)` 가 괄호 없이 되는 이유입니다
- **나머지는 헷갈리면 괄호를 치세요.** 괄호를 친 코드가 진 적은 없습니다

```csharp
bool r = true || false && false;     // 헷갈립니다
bool r = true || (false && false);   // 같은 뜻인데 읽힙니다
```

`&&` 가 `||` 보다 먼저라 둘은 같은 식입니다. 그런데 읽는 사람은 그걸 모릅니다.

대입이 맨 마지막인 것도 이제 말이 됩니다.
`int r = 3 + 4 * 5;` 에서 오른쪽이 **값 하나로 다 줄어든 다음에야** `r` 의 자리에 씁니다.

## `&&` 는 오른쪽을 안 볼 수도 있다

순서 이야기의 마지막입니다. 어떤 부분은 **아예 계산되지 않습니다.**

> **왼쪽만으로 결과가 정해지면 오른쪽은 실행되지 않습니다.**

`&&` 는 왼쪽이 거짓이면 오른쪽이 뭐든 거짓입니다. 그래서 안 봅니다.
`||` 는 왼쪽이 참이면 오른쪽을 안 봅니다. **단축 평가**라고 합니다.

현금이 있고 시간이 되면 간다고 할 때, 현금이 없으면 시간은 확인도 안 하는 것과 같습니다.

이게 단순한 최적화가 아니라 **자주 쓰는 안전장치**입니다.

<x-compare>
<script type="application/json">
{
  "label": "순서가 결과를 바꾼다",
  "tabs": [
    { "label": "이 순서면", "code": "if (player != null && player.Hp > 0)\n{\n    // 안전합니다\n}", "stats": [["player 가 null 이면", "왼쪽에서 false 확정"], ["오른쪽", "실행되지 않음"]], "tone": "good" },
    { "label": "뒤집으면", "code": "if (player.Hp > 0 && player != null)\n{\n    // 터집니다\n}", "stats": [["player 가 null 이면", "왼쪽부터 접근"], ["결과", "NullReferenceException"]], "tone": "bad" }
  ]
}
</script>
</x-compare>

`null` 검사를 먼저 두고 바로 뒤에서 그 객체를 쓸 수 있는 이유가 단축 평가입니다.
순서를 뒤집으면 검사가 있으나 마나입니다.

## 정리

- 식은 우선순위가 높은 곳부터 값으로 바뀌고, 값 하나만 남을 때까지 줄어든다.
- 괄호는 새 규칙이 아니라 그 순서를 직접 정하는 도구다. 헷갈리면 괄호를 쓴다.
- 산술 → 비교 → 논리 → 대입 순이다. 대입이 맨 마지막이라 오른쪽이 다 줄어든 뒤에 쓴다.
- 캐스팅이 나눗셈보다 늦으면 아무 의미가 없다.
- `&&` 와 `||` 는 왼쪽으로 결과가 정해지면 오른쪽을 실행하지 않는다.