---
title: "static 은 객체가 아니라 클래스에 붙는다"
description: "처음 쓴 코드가 static void Main 이었는데, 그 static 이 무엇인지는 아무도 설명하지 않았습니다."
author: "유니얼"
pubDate: 2026-09-09
category: "programming/csharp/advanced"
tags: ["C#"]
canonical: https://blog.unialgames.com/posts/csharp-static/
---
처음 만든 C# 코드는 이렇게 생겼습니다.

```csharp
static void Main(string[] args)
{
    Console.WriteLine("안녕");
}
```

여기에 설명 안 하고 넘어간 것이 두 개 있습니다.

`Main` 앞의 `static` 이 무엇인지, 그리고 `Console` 을 `new` 로 만들지 않았는데 왜 `WriteLine` 을 부를 수 있는지입니다.
앞에서 메서드는 객체에 붙어 있고, 객체는 `new` 로 만든다고 했습니다. 이 두 줄은 그 규칙을 어기는 것처럼 보입니다.

> **`static` 이 붙으면 객체가 아니라 클래스에 붙습니다. 객체를 안 만들어도 부를 수 있습니다.**

## static 은 이렇게 생겼다

필드나 메서드 앞에 단어 하나를 더 붙이는 것이 전부입니다.

<figure class="diagram"><svg viewBox="0 0 620 150" role="img" aria-label="static 필드와 일반 필드의 생김새"><text x="20" y="34" class="mono" font-size="15">public static int Count;</text><text x="20" y="74" class="mono" font-size="15">public int Age;</text><line x1="120" y1="28" x2="330" y2="28" stroke="currentColor" stroke-opacity="0.25" /><text x="340" y="33" font-size="13">클래스에 하나. 객체가 없어도 있습니다</text><line x1="120" y1="68" x2="330" y2="68" stroke="currentColor" stroke-opacity="0.25" /><text x="340" y="73" font-size="13">객체마다 하나. 객체가 있어야 있습니다</text><text x="20" y="122" font-size="13" opacity="0.7">부르는 법도 다릅니다. Dog.Count 와 a.Age 입니다.</text></svg><figcaption>붙는 자리가 다르면 부르는 법도 다릅니다.</figcaption></figure>

```csharp
class Dog
{
    public static int Count;   // 클래스에 하나
    public int Age;            // 객체마다 하나

    public Dog()
    {
        Count = Count + 1;     // 태어날 때마다 하나씩
    }
}
```

부를 때는 이렇게 씁니다.

```csharp
Dog a = new Dog();
Dog b = new Dog();

a.Age = 3;              // 객체를 통해서
Console.WriteLine(Dog.Count);   // 클래스를 통해서. 2
```

`a.Count` 라고 쓰면 컴파일 에러입니다. `Count` 는 `a` 의 것이 아니라 `Dog` 의 것이기 때문입니다.
반대로 `Dog.Age` 도 에러입니다. `Age` 는 객체가 있어야 존재합니다.

## 객체가 늘어나도 하나뿐이다

`Dog` 를 세 마리 만들어 봅니다. `Age` 는 세 개가 생기고 `Count` 는 계속 하나입니다.

<x-flow>
<script type="application/json">
{
  "label": "객체마다 하나 / 클래스에 하나",
  "lines": [
    "Dog a = new Dog();",
    "a.Age = 3;",
    "Dog b = new Dog();",
    "b.Age = 5;",
    "Dog c = new Dog();",
    "Console.WriteLine(Dog.Count);"
  ],
  "cases": [
    {
      "path": [
        { "line": 0, "why": "첫 마리가 태어나면서 생성자가 Count 를 1 로 올립니다.", "state": "a.Age = 0 · Dog.Count = 1" },
        { "line": 1, "why": "a 의 Age 만 바뀝니다. 다른 개와 상관없습니다.", "state": "a.Age = 3 · Dog.Count = 1" },
        { "line": 2, "why": "Age 는 새로 하나 더 생기는데, Count 는 새로 안 생기고 올라갑니다.", "state": "a.Age = 3 · b.Age = 0 · Dog.Count = 2" },
        { "line": 3, "why": "b 의 Age 만 바뀝니다. a 는 그대로입니다.", "state": "a.Age = 3 · b.Age = 5 · Dog.Count = 2" },
        { "line": 4, "why": "세 마리째. Age 는 세 개가 됐고 Count 는 여전히 하나입니다.", "state": "a.Age = 3 · b.Age = 5 · c.Age = 0 · Dog.Count = 3" },
        { "line": 5, "why": "누구의 Count 인지 물을 필요가 없습니다. 하나뿐입니다.", "state": "a.Age = 3 · b.Age = 5 · c.Age = 0 · Dog.Count = 3", "out": "3" }
      ],
      "note": "Age 는 이름이 셋으로 늘어나는데 Count 는 값만 바뀝니다."
    }
  ]
}
</script>
</x-flow>

`Age` 는 개를 만들 때마다 자리가 하나씩 더 생깁니다. `Count` 는 자리가 처음부터 하나고 값만 바뀝니다.
그래서 "몇 마리 만들었나" 같은 것을 담기에 맞습니다. 어느 한 마리의 것이 아니라 `Dog` 전체의 것이기 때문입니다.

## 그래서 Main 이 static 이다

이제 처음 질문으로 돌아갑니다.

프로그램이 시작되는 순간에는 아직 아무 객체도 없습니다. `new` 를 부른 코드가 아직 한 줄도 안 돌았으니까요.
그런데 실행을 시작하려면 메서드를 하나는 불러야 합니다.

객체가 없어도 부를 수 있는 메서드가 필요합니다. 그게 `static` 입니다.

`Console.WriteLine` 도 같은 이유입니다. 화면에 찍는 일은 어느 한 `Console` 객체의 사정이 아니라 프로그램 전체의 일입니다.
`Console` 을 굳이 만들게 하는 것이 더 이상합니다.

```csharp
Console.WriteLine("안녕");     // static
Math.Max(3, 7);                // static
int.Parse("123");              // static
```

앞에서 쓴 것들이 전부 이것이었습니다. 객체를 만든 기억이 없었던 이유입니다.

## static 메서드 안에서는 this 를 쓸 수 없다

이건 규칙이라기보다 결과입니다.

```csharp
class Dog
{
    public int Age;
    public static int Count;

    public static void 출력()
    {
        Console.WriteLine(Count);   // 됩니다
        Console.WriteLine(Age);     // 컴파일 에러
    }
}
```

`Dog.출력()` 은 객체 없이 부를 수 있습니다. 그런데 `Age` 는 객체마다 다른 값입니다.
객체가 없는데 `Age` 를 읽으라고 하면 **누구의 Age 인지** 답할 방법이 없습니다.

그래서 컴파일러가 아예 못 쓰게 막습니다. 실행하다 터지는 것보다 낫습니다.

반대 방향은 됩니다. 객체가 있는 메서드에서 `Count` 를 읽는 것은 문제가 없습니다. `Count` 는 언제나 하나뿐이니까요.

## 언제 생기고 언제 사라지나

객체는 `new` 할 때 생기고, 아무도 안 가리키면 나중에 GC 가 치웁니다. 앞에서 본 그대로입니다.

static 필드는 다릅니다. **그 클래스를 처음 건드리는 순간 딱 한 번** 만들어지고, 그 뒤로 프로그램이 끝날 때까지 남습니다.
치워지지 않습니다. 가리키는 사람이 없어서 치우는 것인데, static 필드는 클래스가 계속 가리키고 있기 때문입니다.

```csharp
class 설정
{
    public static List<string> 기록 = new List<string>();
}
```

여기에 무언가를 계속 넣기만 하면 그 리스트는 프로그램이 끝날 때까지 안 줄어듭니다.
넣은 객체들도 리스트가 붙잡고 있으니 같이 안 치워집니다.

## 편해서 위험하다

`static` 은 어디서든 부를 수 있습니다. 그래서 편합니다.
그런데 어디서든 부를 수 있다는 말은 **어디서든 바꿀 수 있다**는 말이기도 합니다.

```csharp
public static int 점수;
```

이렇게 하나 만들어 두면 프로그램 어느 구석에서든 `점수` 를 고칠 수 있습니다.
값이 이상해졌을 때 어디서 그랬는지 찾을 방법이 없습니다. 객체에 넣어 뒀다면 그 객체를 들고 있는 코드만 보면 됐을 텐데요.

그래서 static 은 이런 것에 씁니다.

- 아무 상태도 안 들고 값만 계산해서 돌려주는 것 (`Math.Max`)
- 정말로 프로그램에 하나뿐이어야 하는 것
- 클래스 전체의 통계처럼 어느 한 객체의 것이 아닌 것

값이 굴러다니는 것을 담기 시작하면 그때부터는 전역 변수입니다.

## 정리

- `static` 이 붙으면 객체가 아니라 **클래스**에 붙습니다. `Dog.Count` 처럼 부릅니다
- 객체를 몇 개 만들든 static 필드는 하나입니다
- 프로그램이 시작될 때는 객체가 없으므로 `Main` 은 static 이어야 합니다
- static 메서드에서 인스턴스 필드를 못 쓰는 것은 **누구의 것인지 답할 수 없어서**입니다
- 클래스를 처음 건드릴 때 한 번 만들어지고 프로그램이 끝날 때까지 남습니다. 치워지지 않습니다