본문 바로가기
모던 C 언어/C언어 예외처리(Try-Catch)

2. 중첩된 Goto 문을 이용한 에러 핸들링

by 커널패닉 2021. 12. 30.
반응형

앞선 포스트에서 Goto 문을 이용해서 try-catch와 유사하게 에러 핸들링을 하는 방법에 대해 살펴보았다. 이번 포스트에서는 Goto 문 여러개를 이용해서 단계별로 에러 핸들링을 하는 방법에 대해 다뤄보려 한다.

특정 함수 안에서 작업 A, B, C가 이뤄진다고 가정해 보자. 이 작업들은 완료가 된 이후 반드시 정리가 되어야 자원 낭비(e.g. 메모리릭)가 발생하지 않는다. 그렇다면 함수를 빠져나가기 전에 A, B, C 작업은 모두 정리가 되어야 한다. C언어에서 If를 사용해서 각 작업을 정리하는 프로세스를 그림으로 그리면 아래와 같다.

위 그림에서 보이는 문제는 크게 두 가지이다.

  1. 불필요한 코드 중복 발생
  2. 휴먼 에러에 의한 버그 발생 확률 증가

불필요한 코드 중복 발생은 작업 정리 프로세스들이 여러 군데에서 사용되기 때문에 발생한다. 중복에 의해 코드 품질이 낮아지며, 코드 가독성이 심각하게 떨어진다. 다른 문제는 여러군데에서 조금씩 다른 방식의 자원 정리가 이뤄지다보니, Human Error에 의한 버그 발생 가능성이 높아진다. 설령 당장 코드를 작성했을 때는 아무런 문제가 없더라도 나중에 코드가 수정됨에 따라서 버그가 발생할 가능성도 높다.

그렇다면 goto 문을 사용하면 앞서 말한 문제들을 해결할 수 있을까? 아래 그림은 goto 문을 사용해서 에러를 처리하는 방법을 표현한 그림이다.

위 그림에서 볼 수 있듯이 goto를 사용하면, 원하는 에러 처리 단계로 바로 이동할 수 있다. 예를 들어서 B작업이 실패한 경우에는 B, C작업 정리를 할 필요가 없으니 A작업만 정리하면 된다. 이 경우 out_b 구역으로 goto를 하면, A작업만 정리하고 return할 수 있다. 따라서 goto문을 사용하면 코드의 중복을 방지할 수 있다.

Goto문을 사용한 에러 핸들링은 오픈소스에서 많이 사용하는 방식이다. 리눅스 커널 등 다양한 프로그램이 goto문으로 에러 핸들링을 처리한다. 아래는 Wayland 레퍼런스 컴포지터인 weston shell의 메인 함수이다. 자세한 동작을 이해하기 보다는 대략적으로 오픈소스에서 어떤 방식으로 goto를 사용해서 에러 처리를 하는지 이해하면 도움이 된다.

WL_EXPORT int
wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_data)
{
      /* .. 생략 .. */
 	display = wl_display_create();
	if (display == NULL) {
		weston_log("fatal: failed to create display\n");
		goto out_display;
	}
    
      /* .. 생략 .. */
 
	if (!signals[0] || !signals[1] || !signals[2] || !signals[3])
		goto out_signals;

      /* .. 생략 .. */

	wet.compositor = weston_compositor_create(display, log_ctx, &wet, test_data);
	if (wet.compositor == NULL) {
		weston_log("fatal: failed to create compositor\n");
		goto out;
	}
    
      /* ..생략 .. */

out:
	wet_compositor_destroy_layout(&wet);

	/* free(NULL) is valid, and it won't be NULL if it's used */
	free(wet.parsed_options);

	if (protologger)
		wl_protocol_logger_destroy(protologger);

	weston_compositor_destroy(wet.compositor);
	weston_log_scope_destroy(protocol_scope);
	protocol_scope = NULL;

out_signals:
	for (i = ARRAY_LENGTH(signals) - 1; i >= 0; i--)
		if (signals[i])
			wl_event_source_remove(signals[i]);

	wl_display_destroy(display);

out_display:
	weston_log_scope_destroy(log_scope);
	log_scope = NULL;
	weston_log_subscriber_destroy(logger);
	weston_log_subscriber_destroy(flight_rec);
	weston_log_ctx_destroy(log_ctx);
	weston_log_file_close();

	if (config)
		weston_config_destroy(config);
	free(config_file);
	free(backend);
	free(shell);
	free(socket_name);
	free(option_modules);
	free(log);
	free(modules);

	return ret;
}
반응형