제 3 부 — 배열



9장 · 동적 배열과 메모리 할당


Memory on Demand



[!attention] 이 장의 목표

- 동적 할당의 마스터allocatable 속성과 allocate·deallocate 구문을 사용하여 실행 중에 메모리를 유연하게 확보하고 해제한다.

- 자동 재할당 활용 — 현대 Fortran의 강력한 편의 기능인 할당가능 대입(allocatable assignment) 시 발생하는 자동 재할당 메커니즘을 이해하고 안전하게 구사한다.


지금까지 다룬 배열은 모두 컴파일 단계에서 크기가 고정되는 정적 배열(static array)이었다. 예를 들어 real :: v(100)과 같이 선언과 동시에 길이가 고정되므로, 프로그램을 빌드하기 전에 필요한 원소 수를 설계자가 미리 완벽하게 알고 있어야만 한다.

그러나 실제 고성능 수치 해석 연산 환경에서는 다음과 같이 실행해 보기 전에는 데이터의 크기를 쉽게 짐작할 수 없는 상황이 일반적이다. 예를 들면, 관측 데이터 처리 프로그램은 사용자가 프로그램을 실행한 후 $n$개의 관측 표본을 입력해야 하는데, 그 다음 실행 시에는 표본 수 $n$이 어떻게 달라질지 예측할 수 없다.

이 장에서는 실행 단계(runtime)에서 배열의 크기를 자유자재로 결정하고 변경할 수 있는 allocatable 속성을 학습한다. 메모리를 동적으로 할당(allocate)하고 안전하게 돌려주는 해제(deallocate) 기법부터, 단순한 대입 연산만으로 배열 크기를 스스로 맞추는 자동 재할당 기능과 메모리 오버헤드를 최소화하며 배열을 효율적으로 확장하는 최적화 기법까지 다룬다.


[!important] 9.1 allocatable 속성과 allocate · deallocate

정적 배열은 선언과 동시에 메모리 크기가 고정되지만, 동적 배열(dynamic array)은 선언할 때 차원(rank) 정보만 지정하고 실제 크기는 비워둔 채 실행 시점에 메모리를 부여받는다. 구문 구조는 아래와 같이 변수에 allocatable 속성을 붙이고 각 차원을 콜론(:)으로 표시하여 선언한다.

real :: v(100)                 ! 컴파일 시점에 길이 100으로 고정되는 정적 배열
real, allocatable :: v(:)      ! 1차원(Rank 1), 크기는 아직 결정되지 않은 동적 배열
real, allocatable :: m(:,:)    ! 2차원(Rank 2), 크기는 아직 결정되지 않은 동적 배열

선언 직후의 allocatable 변수는 아직 실제 메모리 주소를 갖지 못한 미할당(unallocated) 상태이다. 반드시 allocate 문을 통해 실제 메모리 공간을 확보해 주어야만 원소에 접근할 수 있으며, 데이터 처리가 끝나면 deallocate 문으로 메모리를 시스템에 반환해야 한다.

allocate(v(n))        ! 실행 시점의 변수 n만큼의 메모리 길이 확보
! ... 배열 v 사용 및 수치 연산 수행 ...
deallocate(v)         ! 확보했던 메모리를 시스템에 완전히 반환

선언 시점에 지정한 콜론의 개수(차원 또는 rank)는 도중에 바꿀 수 없으나, 각 차원의 실제 길이는 실행 중에 자유롭게 결정할 수 있다. 대규모 물리 시뮬레이션에서 필요한 시점에만 대형 배열을 메모리에 올리고 사용 후 즉시 제거함으로써 시스템 자원(resouce)을 극한으로 최적화할 수 있는 메모리 관리의 핵심 도구다.

동적 배열을 안전하게 제어하기 위한 문법은 다음 단계로 정의된다.

[!note] 메모리 보호 및 예외 처리 안전 규칙 - 이중 할당 및 미할당 해제 금지: 이미 메모리를 부여받은 배열에 또다시 allocate를 호출하거나, 반대로 할당된 적이 없는 배열을 deallocate 하려고 시도하면 프로그램은 런타임 에러(run-time error)를 내며 즉시 강제 종료(crash)된다. 따라서 안전한 코딩을 위해 아래와 같이 allocated 함수로 상태를 돋보기 검사하듯 확인하는 습관을 들이자.

Fortran if (.not. allocated(v)) allocate(v(n)) if (allocated(v)) deallocate(v)

[예제] 실행 단계에서 메모리 크기 정하기

사용자가 실행 중에 입력한 정수 $n$을 그대로 받아 그 크기만큼 배열의 메모리를 동적으로 생성하고, 연산이 끝나면 안전하게 반환하는 가장 표준적인 동적 할당 예제이다.

%%writefile dynamic_basics.f90
program dynamic_basics
  implicit none
  integer :: n, i
  real, allocatable :: v(:)

  read *, n              ! size decided at run time

  allocate(v(n))
  do i = 1, n
     v(i) = real(i)**2
  end do

  print *, "allocated size:", size(v)
  print *, "elements:", v

  deallocate(v)
end program dynamic_basics

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall dynamic_basics.f90 -o dynamic_basics
!echo 5 | ./dynamic_basics

실행 결과:

 allocated size:           5
 elements:   1.00000000  4.00000000  9.00000000  16.0000000  25.0000000

[예제] 할당 실패에 대비하기: stat

컴퓨터의 물리 메모리 크기는 한계가 있으므로, 대규모 격자 데이터나 거대한 행렬을 다룰 때 시스템의 가용 용량을 초과하는 너무 큰 배열을 요청하면 메모리 확보가 거부된다. 이때 예외 처리 안전장치 없이 allocate를 수행하면 프로그램은 예기치 않게 종료된다.

하지만 구문 뒤에 stat= 옵션을 결합해 두면, 할당 실패 시 프로그램이 강제 종료되는 대신 0이 아닌 특정한 에러 코드를 변수에 담아 반환하므로 개발자가 안전하게 사후 처리할 수 있다.

%%writefile alloc_stat.f90
program alloc_stat
  implicit none
  integer, parameter :: i8 = selected_int_kind(18)
  integer(i8) :: request
  integer :: ierr
  real, allocatable :: big(:)

  request = 10_i8**11        ! deliberately too large

  allocate(big(request), stat=ierr)
  if (ierr /= 0) then
     print *, "allocation failed, stat =", ierr
     stop 1
  end if

  print *, "allocation succeeded"
  deallocate(big)
end program alloc_stat

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall alloc_stat.f90 -o alloc_stat
!./alloc_stat

실행 결과:

 allocation failed, stat =        5020
STOP 1

[!warning] [흔한 실수] 큰 메모리를 다루거나 크기를 사용자 입력·파일에서 받는 코드에서는 stat=을 받는 습관을 들여야 한다. stat을 생략하고 할당이 실패하면 프로그램은 곧바로 비정상 종료한다.

[예제] allocated로 상태 확인하기

동일한 allocatable 동적 변수를 해제하지 않은 상태에서 중복으로 할당(allocate)하거나, 이미 해제된 변수를 다시 해제(deallocate)하려고 시도하면 런타임 에러가 발생하며 프로그램이 즉시 비정상 종료된다.

따라서 동적 메모리를 다룰 때는 "현재 이 배열이 메모리에 정상적으로 할당되었는가?"를 사전에 점검해야만 안정성을 확보할 수 있다. Fortran은 이 검사를 정확히 수행하는 allocated() 내장 함수를 제공한다.

%%writefile check_allocated.f90
program check_allocated
  implicit none
  real, allocatable :: vals(:)

  print *, "before:", allocated(vals)

  allocate(vals(3))
  vals = [1.0, 2.0, 3.0]
  print *, "after allocate:", allocated(vals), " size:", size(vals)

  if (allocated(vals)) deallocate(vals)
  print *, "after deallocate:", allocated(vals)
end program check_allocated

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall check_allocated.f90 -o check_allocated
!./check_allocated

실행 결과:

 before: F
 after allocate: T  size:           3
 after deallocate: F

[!warning] [흔한 실수] 런타임 에러를 부르는 동적 할당 실수들 이미 메모리를 할당받은 allocatable 변수에 명시적 해제 없이 allocate를 중복으로 호출하면 '이미 할당됨(already allocated)'이라는 치명적인 런타임 오류가 발생한다. 반대로, 메모리가 아직 확보되지 않은 미할당 상태의 배열에 vals(1) = 0.0과 같이 원소 단위로 접근하거나 deallocate를 시도하는 행위 또한 시스템 예외를 유발하며 프로그램이 즉시 강제 종료된다. 동적 변수의 할당 상태나 메모리 적격성이 모호할 때는 반드시 allocated() 내장 함수로 상태를 먼저 검증(validation)한 뒤 처리하는 습관을 들이자.


[!important] 9.2 할당 가능 대입과 자동 재할당

현대 Fortran에는 코드를 극적으로 간결하게 만들어 주는 강력한 메모리 편의 기능이 존재한다. 아래 문법 규격과 같이 allocatable 속성이 부여된 동적 변수에 배열을 그대로 대입(=)하면, 좌변의 배열이 우변 데이터의 형상(shape)과 크기에 맞춰 컴파일러 전산 시스템 내부에서 자동으로 메모리를 확보한다. 이는 개발자가 번거롭게 allocate 문장으로 크기를 사전에 규정하는 단계를 생략해도 된다는 의미다.

real, allocatable :: u(:)
u = [1.0, 2.0, 3.0]     ! 별도의 allocate 명령 없이도 변수 u는 크기 3으로 자동 할당됨

만약 이미 메모리에 할당된 상태의 변수에 전혀 다른 형상이나 크기를 가진 배열을 대입할 경우, 좌변 배열의 기존 메모리를 스스로 해제한 뒤 새로운 우변의 규격에 맞춰 공간을 완전히 다시 재편한다. 이런 메커니즘을 할당 가능 대입 시의 자동 재할당(reallocation on assignment)이라고 한다.

좌변 변수가 allocatable 속성을 지닌 동적 배열일 때, 대입 연산자(=)가 작동하는 방식은 다음과 같다.

[예제] 대입만으로 메모리 재할당

아래는 명시적인 allocatedeallocate 구문을 전혀 사용하지 않고, 오직 대입 연산자(=)만을 활용하여 배열의 메모리를 최초로 확보한 뒤 할당 크기를 유연하게 바꾸는 예시이다.

%%writefile alloc_assign.f90
program alloc_assign
  implicit none
  real, allocatable :: u(:), w(:)

  u = [1.0, 2.0, 3.0, 4.0]            ! auto-allocated to size 4
  print *, "u size:", size(u), " u:", u

  w = [10.0, 20.0]                    ! size 2
  print *, "w size:", size(w)

  w = [7.0, 8.0, 9.0, 5.0, 6.0]       ! auto-reallocated to size 5
  print *, "w size:", size(w), " w:", w
end program alloc_assign

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall alloc_assign.f90 -o alloc_assign
!./alloc_assign

실행 결과:

 u size:           4  u:   1.00000000  2.00000000  3.00000000  4.00000000
 w size:           2
 w size:           5  w:   7.00000000  8.00000000  9.00000000  5.00000000  6.00000000

[!warning] [흔한 실수] 대입 연산 시 자동으로 배열의 크기가 조정되는 자동 재할당 기능은 오직 좌변의 변수가 동적 할당 배열(allocatable)로 선언되었을 때만 작동한다. 아래 처럼 크기가 미리 정해진 고정 크기 배열에 형상(shape)이나 크기가 다른 값을 대입하려고 시도하면 컴파일 오류가 발생한다.

fortran real :: fixed(3) fixed = [1.0, 2.0] ! ERROR: shape (3) vs (2) gfortran 컴파일러는 이런 형상 불일치 문제를 컴파일 단계에서 감지하여 차단한다. 이때 화면에는 다음 구체적인 에러 메시지가 출력된다. Different shape for array assignment at (1) on dimension 1 (expected 3 and got 2) 따라서 배열 간의 대입 연산을 수행할 때는 좌변 변수가 고정 크기 배열인지 아니면 재할당이 가능한 동적 배열인지 명확히 인지하고, 고정 크기 배열일 경우 우변의 데이터 개수와 반드시 일치시켜야 한다.

자동 재할당은 Fortran 2003 이후 표준이다. 일부 컴파일러나 옛 호환 옵션(예: gfortran의 -fno-realloc-lhs)에서는 이 동작이 비활성되어, 좌변 크기>가 유지되고 값이 잘리는 등 다른 결과가 나올 수 있는데, 표준 모드(-std=f2018)로 컴파일하면 안전하다.

[예제] 배열 크기 동적 확장하기 (append)

프로그래밍을 하다 보면 조건에 맞는 원소를 하나씩 읽어들이면서 배열의 길이를 늘려나가는 작업이 자주 필요하다. Fortran에서는 배열 생성자와 자동 재할당 대입 기능을 결합하면 이를 매우 간결하게 코드로 표현할 수 있다.

아래 프로그램은 $1$부터 $20$까지의 정수 중, 제곱한 값이 짝수인 수들만 골라 배열에 차례대로 할당하는 예제이다. 수집될 데이터의 최종 개수를 미리 예측할 수 없으므로, 처음에는 크기가 $0$인 빈 배열로 시작한다.

%%writefile grow_array.f90
program grow_array
  implicit none
  integer, allocatable :: collected(:)
  integer :: i, sq

  allocate(collected(0))             ! start empty (size 0)

  do i = 1, 20
     sq = i*i
     if (mod(sq, 2) == 0) then
        collected = [collected, sq]  ! grow by one element
     end if
  end do

  print *, "count:", size(collected)
  print *, "values:", collected
end program grow_array

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall grow_array.f90 -o grow_array
!./grow_array

실행 결과:

 count:          10
 values:           4   16   36   64  100  144  196  256  324  400

collected = [collected, sq] 구문이 실행될 때, 컴퓨터 내부에서는 "기존 배열의 모든 원소와 새로운 원소 sq 하나"를 결합한 새로운 임시 배열을 먼저 생성한다. 그 후, 자동 재할당 기능으로 좌변에 있던 collected 배열의 크기가 자동으로 한 칸 확장되면서 그 임시 배열의 값들이 그대로 대입된다.


[!important] 9.3 배열 크기에 따른 수렴 속도 및 정밀도 비교

배열의 크기를 물리적으로 키워 격자를 촘촘하게 구성하는 것만으로는 시뮬레이션의 정확도가 무한정 향상되지 않는다. 수치 해석 과정에서는 수학적 알고리즘 자체가 내포한 오차 수준과 컴퓨터 하드웨어가 가지는 부동소수점 정밀도의 한계선이 복합적으로 맞물려 최종 정확도를 결정하기 때문이다.

고성능 과학 계산과 기상 시뮬레이션에서 왜 상황에 맞는 유연한 동적 배열(allocatable)의 확보와 정밀한 종류 선택(kind)이 필수적인지 다음 예제와 시각화 결과를 통해 명확히 알 수 있다.

[예제] 배열 크기에 따른 수렴 속도 및 정밀도 비교

이 장의 서두에서 제시했던 문제 2 격자 해상도를 2배씩 키우며 수렴성 관찰하는 것을 직접 구현하고 검증해 보자. 수학적으로 참값이 $\pi \approx 3.141592653589793$로 알려진 다음의 정적분 식을 활용한다.

$$\int_0^1 \frac{4}{1+x^2}\,dx = \pi$$

이 적분을 수치적으로 계산하기 위해 가장 직관적인 격자 적분법인 사다리꼴 법칙(trapezoidal rule)을 적용한다. 구간의 수 $n$을 2부터 시작하여 매 루프마다 2배씩 기하급수적으로 증가($n = 2, 4, 8, \dots, 65536$)시킨다. 이때 격자 점의 개수에 맞춰 매번 정확히 길이가 $n+1$인 가변 표본 배열이 요구된다.

동일한 계산을 단정밀도(real32)와 배정밀도(real64)로 나란히 수행하여, 격자를 촘촘하게 키울 때 누적되는 컴퓨터 부동소수점 오차가 어떻게 다르게 변하는지 확인해 보자. 반복문(do k = 1, 16) 내부에서 각 차원마다 격자 배열이 어떻게 생성되고 소멸되는지 추적해 보자.

%%writefile convergence.f90
program convergence
  implicit none
  integer, parameter :: dp = selected_real_kind(15, 307)
  real,     allocatable :: xs(:), ys(:)     ! single precision
  real(dp), allocatable :: xd(:), yd(:)     ! double precision
  real     :: hs, approx_s
  real(dp) :: hd, approx_d, exact
  integer  :: k, n, i, u

  exact = acos(-1.0_dp)                      ! true value = pi

  open(newunit=u, file="convergence.csv", status="replace", action="write")
  write(u, '(a)') "n,err_single,err_double"

  do k = 1, 16
     n = 2**k
     allocate(xs(n+1), ys(n+1), xd(n+1), yd(n+1))

     hs = 1.0 / real(n)
     hd = 1.0_dp / real(n, dp)
     do i = 0, n
        xs(i+1) = real(i) * hs
        xd(i+1) = real(i, dp) * hd
     end do
     ys = 4.0 / (1.0 + xs**2)
     yd = 4.0_dp / (1.0_dp + xd**2)

     approx_s = hs * (sum(ys) - 0.5 * (ys(1) + ys(n+1)))
     approx_d = hd * (sum(yd) - 0.5_dp * (yd(1) + yd(n+1)))

     write(u, '(i7,",",es16.8,",",es16.8)') n,                 &
          abs(real(approx_s, dp) - exact), abs(approx_d - exact)

     deallocate(xs, ys, xd, yd)
  end do

  close(u)
  print *, "convergence.csv written"
end program convergence

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall convergence.f90 -o convergence
!./convergence

실행 결과 생성된 convergence.csv의 일부:

n,err_single,err_double
      2,  4.15927490E-02,  4.15926536E-02
      8,  2.60415872E-03,  2.60415910E-03
    256,  2.05834443E-06,  2.54313151E-06
   4096,  5.64259938E-07,  9.93410953E-09
  65536,  2.53518159E-06,  3.88196142E-11

반복문에서 루프가 돌 때마다 네 개의 동적 배열(xs, ys, xd, yd)이 매번 새 크기로 allocate되고, 연산이 끝나는 블록 말단에서 deallocate되어 다음 번 루프의 가변 격자 할당에 대비한다. 만약 이 명시적 해제 과정을 단 한 줄이라도 누락하면, 두 번째 루프(k=2)에 진입하는 순간 중복 할당 예외가 발생하고 프로그램이 즉시 강제 종료된다.

코드 내부의 4.0_dp / (1.0_dp + xd2)와 같은 수식 및 sum 내장 함수는 8장에서 배운 전체 배열 연산(array expression)을 기반으로 작동하므로 루프 없이 초고속 병렬 계산이 수행된다.

출력된 convergence.csv 파일의 데이터를 해석하면 다음과 같다.

수집된 격자별 오차 파일(convergence.csv)을 파이썬 코딩으로 수렴 그래프를 만들어 보자. 가로축과 세로축을 모두 로그 스케일로 설정한다.

import csv
import matplotlib.pyplot as plt

ns, es, ed = [], [], []
with open("convergence.csv") as f:
    reader = csv.reader(f)
    next(reader)                       # skip header
    for row in reader:
        ns.append(int(row[0]))
        es.append(float(row[1]))
        ed.append(float(row[2]))

plt.figure(figsize=(8, 5))
plt.loglog(ns, es, "o-", label="single precision (real)")
plt.loglog(ns, ed, "s-", label="double precision (dp)")
plt.xlabel("n (number of intervals)")
plt.ylabel("|approximation - pi|")
plt.title("Trapezoidal integration error vs array size n")
plt.legend()
plt.grid(True, which="both")
plt.savefig("convergence.png", dpi=120)
plt.show()

![[Pasted image 20260711201524.png]]


[!important] 오류 학습

[예제] 반복문 내 명시적 deallocate 누락

반복문 내부에서 매 루프마다 해상도나 크기가 유동적으로 변하는 동적 배열을 새로 생성하여 연산하는 루프 구조는 수치 해석 프로그램에서 매우 흔하게 활용된다.

아래는 매 루프마다 allocate를 통해 새로운 물리 공간을 확보하면서도, 사용이 끝난 자원을 반환하는 deallocate 명령을 빠트렸을 때 발생하는 치명적인 런타임 오류 현상이다.

%%writefile loop_no_dealloc.f90
program loop_no_dealloc
   implicit none
   real, allocatable :: x(:)
   integer :: k, n

   do k = 1, 3
      n = 2*k
      allocate(x(n))     ! 2nd iteration: x is still allocated
      x = real(k)
      print *, "k =", k, " size =", size(x)
      ! deallocate(x) is missing
   end do
end program loop_no_dealloc

컴파일·실행:

!gfortran -O2 -std=f2018 -Wall loop_no_dealloc.f90 -o loop_no_dealloc
!./loop_no_dealloc

반복문의 첫 회 루프는 정상으로 보이지만 두 번째 루프에서 멈춘다.

 k =           1  size =           2
At line 8 of file loop_no_dealloc.f90
Fortran runtime error: Attempting to allocate already allocated variable 'x'

첫 번째 루프(k = 1)에서는 변수 x가 미할당 상태였으므로 allocate(x(2))가 정상적으로 작동되어 크기 2의 격자가 생성되고 값이 출력된다.

앞에서 배운 동적 배열의 자동 해제(auto-deallocate) 기능은 반복문 블록의 끝이 아니라, 해당 변수가 선언된 프로그램 단위(여기서는 메인 프로그램)가 완전히 끝나는 시점에 비로소 작동한다. 따라서 첫번째 루프가 끝나고 두 번째 루프(k = 2)의 헤더를 통과하여 다시 allocate(x(4))를 마주하는 순간 문제가 발생한다. 변수 x는 여전히 크기 2인 메모리를 할당 받은 상태이므로, 결과적으로 컴파일러는 "이미 메모리를 점유하고 있는 동적 변수에 대고 또다시 할당을 감행했다"고 판단하여 Attempting to allocate already allocated variable 메시지를 내고 프로그램을 강제 종료한다.

이 문제는 명시적 자원 반환이라는 정석적인 방법으로 해결할 수 있다. 루프가 끝나기 직전, deallocate(x)를 명시적으로 호출하여 다음 반복 루프의 신규 할당이 방해받지 않도록 메모리를 깨끗하게 비우는 것이다.

   do k = 1, 3
      n = 2*k
      allocate(x(n))
      x = real(k)
      print *, "k =", k, " size =", size(x)
      deallocate(x)      ! 메모리를 즉시 청소하여 다음 회차의 새로운 allocate에 완벽히 대비한다
   end do

[!important] 요약


[!important] 연습 문제

  1. 정수 n을 표준 입력으로 받아, 1부터 n까지의 정수를 담는 allocatable 배열을 만들고 그 합을 출력하라. 마지막에 deallocate도 호출하라.
  2. 길이 미정의 실수 배열 a(:)를 선언하고, a = [3.5, 1.2, 4.8, 2.1]처럼 할당가능 대입으로 채운 뒤 size(a)maxval(a)를 출력하라.
  3. allocatable 배열 x에 대해, 할당 전·후·해제 후 각각 allocated(x)의 값을 출력하는 프로그램을 작성하라.
  4. 행 수와 열 수를 각각 입력으로 받아 m(:,:)allocate하고, 모든 원소를 0.0으로 채운 뒤 형상을 shape(m)으로 출력하라.
  5. 너무 큰 크기를 일부러 요청하고 stat=을 받아, 성공이면 크기를, 실패면 안내 문구를 출력하라.
  6. 빈 배열(크기 0)에서 시작해, 1~30 중 3의 배수만 v = [v, k]로 모아 개수와 값을 출력하라.
  7. 정수 n을 받아 길이 n+1인 실수 배열에 $x_i = i/n$ ($i=0,\dots,n$)을 채우고, y = sin(2*pi*x)를 계산해 wave.csv로 저장하라. Python으로 선그래프를 그려라.
  8. allocatable 배열 v를 크기 4로 할당해 채운 뒤, 같은 변수에 크기 7짜리 값을 대입(자동 재할당)하고, 재할당 전후의 size(v)를 출력해 크기가 바뀌었음을 보여라.
  9. 예제 9.3.3을 변형해, 용량을 두 배가 아니라 약 1.5배(올림)로 늘리도록 바꾸고, 100개를 담는 동안 재할당이 몇 번 일어나는지 세어 출력하라.
  10. allocatable 배열 a, b를 받아(대입으로 초기화), c = [a, b]로 이어 붙인 배열을 만들고 size(c)size(a)+size(b)와 같은지 확인하라.