SW 그리고 아빠 엔지니어링 중...

아는 만큼 보이고, 경험해봐야 알 수 있고, 자꾸 써야 내 것이 된다.

언어(공통)

[C/C++] #define과 const 차이

보리남편 김 주부 2022. 12. 21. 00:36

 

#define을 쓸 것인가? const를 쓸 것인가?
내 기억을 더듬어보면 C/C++ 코드는 #define을 java 코드는 const를 썼던 것 같다.
큰 의미를 두고 사용한 것이 아니라 기존 코드에서 #define이 있으면 #define을 const 면 구색에 맞게 const로 선언하여 사용했던 것 같다. 지금이라도 이해하고 좀 더 용도에 맞게 사용해보고자 한다.

 

Const와 Define의 차이
  • 가장 큰 차이는 const는 메모리를 할당받고, #define은 메모리를 차지하지 않는다.
  • 예를 들어 const int 하면 read-only data memory에 그 값이 올라가는 것이고 define은 pre-compiling에서 치환된다. (즉 메모리를 차지하지 않는다.)
  • ex) Const int a = 125 이면 Text section 즉 Code memory (ROM 혹은 flash memory)에 들어가게 된다.

임베디드 F/W RAM/ROM 메모리 구조

 

위 메모리 구조에서 확인되듯이 const로 선언된 변수는 ROM 영역에 할당되기에 const가 늘어나면 명령어코드가 늘어나며, 자주 호출하게 되면 성능이 저하될 수 있습니다. 말도 안 되게 많이 호출되지 않는 한 성능 저하를 체감할 순 없겠지만 const로 선언된 변수가 자주 호출되고 있다면 const 제거 혹은 다른 대체 방법을 찾아보는 것도 좋을 것 같다.

 

전처리란?

더보기

C/C++에서 #이 붙은 요소는 전처리기(preprocessor)라고 불리며, 컴파일을 하기 전에 미리 처리되는 역할을 한다.

 

#define 은 전처리기 중 하나로 만약 선언된 문자가 있다면 해당 문자는 컴파일 시 지정한 문자로 변경됩니다.

 

너무 성의없게 설명한 것 같은데 실제로 컴파일러가 치환을 하고 있는지 확인해보자.

#include <stdio.h>

#define F1 13
const int g1 = 1;

int main() {

    printf ("a is %d\n", F1);

    printf("g1 is %d\n", g1);
    return 0;
}

F1을 13으로 정의하고 F1 값을 출력하는 간단한 프로그램이다.

 

어셈블리어 코드로 생성하는 방법

치환되는지 확인하기 위해서는 위 코드를 어셈블리어 코드로 컴파일해보자.

더보기

//makefile

make 파일에서 컴파일해서 나온 main.o 파일을 as 명령을 통해서 어셈블리어로 변환할 수 있다.

main.i: main.c
	cpp main.c > main.i

main.s: main.i
	gcc -S main.i

main.o: main.s
	as -o main.o main.s

all: main.o
	gcc -o main main.o

 

변환된 main.s 전체 내용 확인

더보기
	.file	"main.c"
	.globl	_g1
	.section .rdata,"dr"
	.align 4
_g1:
	.long	1
	.def	___main;	.scl	2;	.type	32;	.endef
LC0:
	.ascii "a is %d\12\0"
LC1:
	.ascii "g1 is %d\12\0"
	.text
	.globl	_main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
LFB10:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$16, %esp
	call	___main
	movl	$13, 4(%esp)
	movl	$LC0, (%esp)
	call	_printf
	movl	$0, %eax
	movl	%eax, 4(%esp)
	movl	$LC1, (%esp)
	call	_printf
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
LFE10:
	.ident	"GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
	.def	_printf;	.scl	2;	.type	32;	.endef

 

 

main.s 코드에서 확인해야 할 내용 정리

call ___main : 여기서 main 함수 호출

//LC0,1 은 ascii 표출 출력 임
LC0: .ascii "a is %d\12\0"
LC1: .ascii "g1 is %d\12\0"

$는 주소, %는 레지스터, ()는 간접 주소를 의미

printf를 두 번 하는데 아래 과정을 보면 F1은 없고 바로 13으로 치환된 값이 사용하고 있는 것을 확인할 수 있다.
movl $13, 4(%esp)  //13을 esp 레지스터에서 4byte 더한 곳에 넣는다.(보통 레지스터가 4byte이기에 다음 레지스터에 넣겠다는 의미)
movl $LC0, (%esp)
call _printf
movl $1, %eax        // define과 달리 const에서는 esp 레지스터에 넣기 전에 0을 eax 레지스터에 넣고
movl %eax, 4(%esp) // eax 레지스터가 esp 다음 레지스터에 넣은 것을 확인할 수 있다.
movl $LC1, (%esp)
call _printf
와우~ 그렇다면 const를 모두 define으로 바꿀 일만 남았군..

 

그럼 Define을 써야 하나?
위 main.s 에서 보면 const  값이 _g1으로 1이 저장되는 것을 확인한 것처럼 
    .globl _g1
    .section .rdata,"dr"
_g1: .long 1

const를 사용하면 디버깅 심벌이 생성되기 때문에 디버깅 시 watch window를 이용해서 상수의 값을 확인하기 편하지만 Define을 사용하면 치환이 되기에 디버깅 하기 불편한 부분이 있다.

 

그리고 매크로 함수로도 많이 사용된다.

#define ADD(a,b) a+b
(i) : 함수 호출이 잦으면 함수 호출 비용이 증가하게 되는데 전처리로 된 함수를 코드 전역에 많이 호출되어 이러한 비용을 증가시키고 있는 부분은 없는지 검토가 필요하다. 

 

728x90