cheoly's language study blog

[실전] 구글링보다 10배 빨랐다: AI로 5분 만에 만든 C언어 로그 함수

C언어
반응형
SMALL

오늘 LabVIEW 프로젝트를 진행하던 중, 외부에서 돌아가는 C언어 프로그램의 내부 상태를 확인해야 하는 상황이 생겼습니다. 핵심 로직은 LabVIEW였지만, 디버깅을 위해 C언어 쪽에서 로그(Log)를 실시간으로 찍어주는 함수가 급하게 필요했죠.

과거 같았으면 'C언어 파일 로그 저장', 'Timestamp 함수' 등을 구글링하며 수십 개의 블로그를 뒤졌을 테지만, 오늘은 AI를 통해 단 5분 만에 해결했습니다.

1. 번거로운 '기초 공사'를 AI에게 맡기다

C언어에서 로그 시스템을 하나 만들려면 은근히 손이 많이 갑니다.

  • 현재 시간을 가져오는 함수 호출
  • 파일 포인터 관리 및 예외 처리
  • 가변 인자(Variable Arguments) 처리 등...

새로 짤 수도 있지만, "지금 당장 디버깅이 급한" 상황에서는 이 과정이 다 시간 낭비입니다. AI에게 내가 필요한 로그 형식을 설명하자마자, 제가 딱 원하던 형태의 함수를 뱉어내더군요.

2. 구글링보다 AI가 빨랐던 이유

구글링은 내 상황에 딱 맞는 코드를 찾기 위해 '검색 -> 클릭 -> 코드 확인 -> 수정 -> 내 코드에 이식'이라는 과정을 반복해야 합니다. 하지만 AI는 달랐습니다.

  • 맥락 이해: "LabVIEW와 연동되는 프로그램이니 이런 형식이 좋겠어"라고 말하면 알아서 맞춰줍니다.
  • 즉시 실행 가능: 수정할 필요 없이 바로 컴파일이 가능한 코드를 제공하므로, 곧바로 LabVIEW 디버깅에 집중할 수 있었습니다.

3. 도구가 시간을 벌어줄 때 일어나는 변화

오늘 제가 절약한 시간은 최소 1~2시간입니다. 그 시간 동안 저는 로그 함수를 짜는 대신, 실제 LabVIEW 프로그램의 로직 결함을 찾는 데 집중할 수 있었습니다.

이것이 제가 생각하는 AI 시대 개발자의 경쟁력입니다. '로그 함수를 잘 짜는 것'보다 '로그 함수를 빠르게 확보하고, 본질적인 시스템 디버깅에 더 많은 시간을 투입하는 것' 말이죠.

4. 실제 적용한 코드

void write_mes_log(const char *message) {
    time_t now;
    struct tm *t;
    char dir_path[256];
    char file_path[256];

    time(&now);
    t = localtime(&now);

    // 1. 랩뷰가 만든 폴더 하위에 '년_월' 폴더 경로 생성
    sprintf(dir_path, "d:\\log\\mes_pc\\%04d_%02d", t->tm_year + 1900, t->tm_mon + 1);

    // 2. 월별 폴더 생성 (이미 있으면 무시됨)
    _mkdir(dir_path);

    // 3. 파일명: 날짜_시간 (분, 초 제외)
    sprintf(file_path, "%s\\%04d%02d%02d_%02d.txt", 
            dir_path, 
            t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, 
            t->tm_hour);

    FILE *fp = fopen(file_path, "a");
    if (fp != NULL) {
        // 실제 로그 기록 (상세 시간 포함)
        fprintf(fp, "[%02d:%02d:%02d] %s\n", t->tm_min, t->tm_sec, message);
        fclose(fp);
    }
}

 

5. 문제 발생 시 해결방법 제시

문제가 발생 시 문제를 설명하면 바로 문제점과 해결법 제시.


마치며

혹시 아직도 사소한 함수 하나를 만들기 위해 구글 검색창과 씨름하고 계신가요? AI를 도구로 인정하고 부리는 순간, 여러분의 개발 시계는 남들보다 훨씬 빠르게 돌아가기 시작할 것입니다.

오늘 만든 로그 함수 덕분에 LabVIEW 디버깅도 무사히 마칠 수 있었습니다. 역시 최고의 도구는 내 시간을 아껴주는 도구입니다.

반응형
LIST

C언어의 진짜 난이도는 문법이 아니라 '보이지 않는 규칙'에 있다

C언어
반응형
SMALL

흔히 C언어를 배울 때 가장 큰 벽으로 '포인터'를 꼽습니다. 하지만 현업에서 수만 줄의 C 코드를 마주하다 보면, 정작 우리를 괴롭히는 것은 포인터 문법 그 자체가 아닙니다. 진짜 사고는 문법이 강제하지 않는 '개발자 간의 암묵적 약속'이 깨질 때 발생합니다.

1. 메모리 소유권(Ownership)의 모호함

C언어에서 가장 흔하면서도 치명적인 사고는 "이 메모리를 누가 치울 것인가?"에 대한 오해에서 시작됩니다.

함수가 동적으로 할당된 포인터를 반환할 때, 그 메모리를 해제(free)할 책임이 함수를 부른 쪽에 있는지, 아니면 별도의 관리 함수가 있는지 코드만 봐서는 알 수 없는 경우가 많습니다. 이 '암묵적 약속'이 어긋나는 순간, 메모리 누수(Memory Leak)나 이미 해제된 메모리에 접근하는(Dangling Pointer) 대형 사고가 터집니다.

2. '작동만 하는 코드'가 만드는 부수 효과(Side Effect)

C언어는 자유도가 높습니다. 어디서든 전역 변수에 접근할 수 있고, 포인터 연산으로 메모리 경계를 넘나들 수 있습니다. 하지만 이 자유는 곧 '독'이 됩니다.

특정 함수를 호출했는데 예상치 못한 곳의 값이 변해있다면, 그 시스템은 이미 통제력을 잃은 것입니다. LabVIEW에서 로컬 변수를 남발하여 데이터 흐름(Data Flow)을 깨뜨리는 것과 본질적으로 같은 실수입니다.

3. 방어적 코딩: 문법 너머의 강제성

결국 C언어에서 사고를 줄이는 유일한 방법은, 언어가 강제하지 않는 규칙을 설계자가 스스로 강제하는 것뿐입니다.

  • const의 적극적 활용: 변경되지 않아야 할 데이터는 철저히 보호합니다.
  • 명확한 인터페이스: 메모리 할당과 해제의 주체를 함수 이름이나 주석으로 명확히 규정합니다.
  • 구조체 중심 설계: 흩어진 변수들을 구조체로 묶어 데이터의 이동 경로를 단순화합니다.

마치며

C언어 실력은 얼마나 복잡한 포인터 연산을 하느냐로 결정되지 않습니다. 얼마나 '예측 가능한 코드'를 짜느냐가 진짜 실력입니다.

문법은 도구일 뿐입니다. 그 도구로 사고를 낼지, 견고한 탑을 쌓을지는 결국 여러분이 설정한 '보이지 않는 규칙'들에 달려 있습니다.

반응형
LIST

AI가 코드를 짤수록, 설계자의 '보이지 않는 선'은 더 뚜렷해야 한다

AI
반응형
SMALL

최근 C, C++, LabVIEW 등 다양한 환경에서의 사고 패턴을 분석하며 한 가지 확신이 들었습니다. 도구는 진화하고 언어는 편리해졌지만, 사고(Accident)가 발생하는 근본적인 원리는 놀라울 정도로 변하지 않았다는 사실입니다.

특히 AI가 코드를 대신 짜주는 지금, 우리는 구현의 속도에 취해 설계의 본질을 놓치고 있는지도 모릅니다. 오늘은 AI 시대에 오히려 더 강조되어야 할 '설계자의 경계선'에 대해 이야기해 보고자 합니다.

AI와 인간 개발자가 협업하며 소프트웨어 설계도를 검토하는 모습


1. 편리함이 가린 '설계의 실종'

과거 C언어로 포인터를 만지거나 LabVIEW에서 복잡한 데이터 흐름을 설계할 때는 '긴장감'이 있었습니다. 한 줄의 실수가 시스템 전체를 멈출 수 있다는 공포가 역설적으로 꼼꼼한 설계를 강제했기 때문입니다.

하지만 AI는 "자, 여기 있습니다"라는 말과 함께 수백 줄의 코드를 순식간에 내놓습니다. 이때 설계자는 '감독'이 아닌 '관객'이 되기 쉽습니다. 내가 고민해서 세운 설계의 벽들이 AI의 편의성 앞에서 너무나 쉽게 허물어지는 순간입니다.

2. 사고가 반복되는 3가지 지점

복잡한 스파게티 코드와 정돈된 모듈형 구조의 시각적 대비

 

언어와 도구가 바뀌어도 사고가 숨어드는 자리는 늘 똑같습니다.

① 상태(State)의 무분별한 공유

C언어의 전역 변수가 위험한 이유는 '누가, 언제' 값을 바꿨는지 알 수 없기 때문입니다. AI에게 기능을 요청하면 종종 구현의 편의를 위해 클래스 멤버 변수를 마구잡이로 참조하거나 전역 상태에 의존하는 코드를 생성합니다.

https://cheolystudy.tistory.com/152

 

사고를 줄이는 C 코드의 기준과 습관

C를 오래 쓰는 사람들의 코드를 보면문법이 특별히 화려하지는 않다.대신 공통적으로 느껴지는 게 있다.“이 코드는 함부로 터지지 않겠다.”그 차이는 재능이 아니라기준과 습관에서 나온다.C

cheolystudy.tistory.com

  • 결과: 기능은 당장 작동하지만, 유지보수 단계에서 특정 값이 왜 변했는지 추적할 수 없는 '디버깅 지옥'을 만듭니다.

② 예외 처리(Exception)의 실종

AI는 '성공하는 경로(Happy Path)'를 그리는 데 최적화되어 있습니다. 하지만 현장에서 사고가 터지는 지점은 항상 0.1%의 예외 상황입니다.

  • 결과: LabVIEW에서 에러 클러스터를 무시하고 선을 이었을 때 발생하는 참사처럼, AI가 생략한 예외 처리 코드는 운영 환경에서 거대한 시한폭탄이 됩니다.

https://cheolystudy.tistory.com/148

 

LabVIEW 구조를 살리는 최소한의 설계 원칙

LabVIEW 프로젝트가 무너질 때를 보면대부분 기술이 부족해서가 아니다.“일단 되니까”라는 선택이조금씩 쌓였을 뿐이다.이번 글에서는대규모 프레임워크나 복잡한 패턴 이야기는 하지 않는다.

cheolystudy.tistory.com

③ 제어권을 잃은 '블랙박스'

"AI가 짰으니 검증되었겠지"라는 생각은 과거 "컴파일러가 에러를 안 냈으니 문제없겠지"라는 초보적인 발상과 다르지 않습니다. 내가 한 줄씩 검토하며 장악하지 못한 코드는 내 시스템 안에 존재하는 '제어할 수 없는 블랙박스'일 뿐입니다.

https://cheolystudy.tistory.com/143

 

AI가 만든 코드, 내가 바로 머지하면 안 되는 이유

AI가 코드를 써주는 시대가 됐다.함수 하나, 로직 하나 정도는 몇 초면 뚝딱 나온다.처음엔 솔직히 감탄이 먼저 나온다.“이 정도면 그냥 써도 되겠는데?”하지만 실무에서 몇 번 겪고 나면이 생

cheolystudy.tistory.com

 

3. 설계자는 '코더'가 아니라 '감독'이어야 한다

이제 개발자의 진짜 경쟁력은 코드를 직접 치는 속도가 아닙니다. AI가 가져온 파편화된 기능들을 어떤 '구조' 안에 안전하게 배치하느냐를 결정하는 능력에 있습니다.

C++의 객체 지향 원칙을 지키려 애쓰고, LabVIEW의 데이터 흐름을 엄격하게 격리하던 그 '기본기'를 AI 코드를 검토할 때도 똑같이 적용해야 합니다.

기술의 파도를 주도적으로 제어하는 설계자의 모습


마치며

기술(Tool)은 선택의 문제일 뿐입니다. 하지만 그 기술을 담는 그릇인 설계(Design)는 책임의 영역입니다.

AI라는 강력한 엔진을 달았을 때일수록, 설계자는 운전대를 더 꽉 잡아야 합니다. 여러분은 지금 AI가 건네준 코드 위에 여러분만의 '보이지 않는 선'을 제대로 긋고 계신가요?

반응형
LIST

사고는 도구가 아니라 선택에서 시작된다

AI
반응형
SMALL

AI를 쓰든,
LabVIEW를 쓰든,
C나 C++을 쓰든
사고가 나는 장면은 이상하게 닮아 있다.

언어도 다르고,
환경도 다른데
결과는 비슷하다.

  • 왜 여기서 터졌는지 모르겠고
  • 고쳐도 불안하고
  • 다시 손대기 싫어진다

이 글은
그 이유를 기술이 아니라
선택의 관점에서 정리한 기록이다.


사고는 항상 “괜찮겠지”에서 시작된다

https://cheolystudy.tistory.com/145

 

AI 코드로 실제로 사고 나는 순간들

AI가 만든 코드는 대부분 멀쩡해 보인다.컴파일도 되고, 테스트도 통과하고, 리뷰에서도 큰 지적이 없다.그래서 더 위험하다.실무에서 문제 되는 순간들은대개 “아무도 의심하지 않았던 코드”

cheolystudy.tistory.com

https://cheolystudy.tistory.com/144

 

AI 코드, 어디까지 맡기고 어디부터 사람이 볼까?

AI를 쓰다 보면 어느 순간 이런 고민이 생긴다.“이 정도면 AI가 다 해줘도 되는 거 아닌가?”실제로 단순한 코드 작성이나 반복 작업은이미 AI가 사람보다 빠르고 정확하다.문제는 경계선이다.어

cheolystudy.tistory.com

 

지금까지 다뤘던 모든 사례를 돌아보면
출발점은 거의 같다.

  • AI가 짜줬으니까
  • LabVIEW에서 흔한 구조니까
  • C에서는 원래 이런 식이니까
  • C++이면 알아서 안전하겠지

이 한 줄의 방심이
시간이 지나며
사고로 형태를 바꾼다.

도구는 다르지만
판단은 항상 사람 쪽에서 먼저 흔들린다.


언어가 달라도 반복되는 패턴

https://cheolystudy.tistory.com/147

 

LabVIEW 프로젝트가 커질수록 반드시 무너지는 구조

LabVIEW로 프로젝트를 시작할 때는 항상 단순하다.VI 몇 개 만들고, 프론트 패널 붙이고,“일단 돌아가네?”에서 출발한다.문제는 그 다음이다.기능이 하나둘 늘고,장비가 추가되고,요구사항이 바

cheolystudy.tistory.com

https://cheolystudy.tistory.com/151

 

현장에서 가장 많이 터지는 C 사고 패턴들

C로 사고가 났다는 얘기를 들으면원인은 매번 새로워 보이지만,코드를 열어보면 장면은 거의 비슷하다.놀라운 건이 사고들이 대부분“어려운 문제”에서 나오지 않는다는 점이다.아주 사소해

cheolystudy.tistory.com

https://cheolystudy.tistory.com/152

 

사고를 줄이는 C 코드의 기준과 습관

C를 오래 쓰는 사람들의 코드를 보면문법이 특별히 화려하지는 않다.대신 공통적으로 느껴지는 게 있다.“이 코드는 함부로 터지지 않겠다.”그 차이는 재능이 아니라기준과 습관에서 나온다.C

cheolystudy.tistory.com

 

AI, LabVIEW, C, C++
전부 다른 영역이지만
사고 패턴은 놀라울 정도로 비슷하다.

  • 책임이 어디 있는지 모른다
  • 수명이 코드에 드러나지 않는다
  • 실패를 전제로 설계하지 않는다
  • “나중에 정리하자”가 누적된다

이건 언어 문제가 아니다.
설계를 미룬 선택의 결과다.


안전한 코드는 공통된 질문에서 시작된다

https://cheolystudy.tistory.com/155

 

사고를 부르는 C++ 코드 패턴들

C++ 사고 코드를 보면대부분 “몰라서” 그렇게 쓴 게 아니다.바쁘고일정에 쫓기고일단 돌아가니까그 결과로비슷한 패턴들이 반복해서 쌓인다.아래는 현장에서 정말 자주 보이는,그리고 시간이

cheolystudy.tistory.com

https://cheolystudy.tistory.com/154

 

사고를 줄이는 C++ 설계 기준

C++에서 사고를 줄이는 방법은새로운 문법을 더 배우는 데 있지 않다.이미 대부분 알고 있다.문제는 그걸 기준으로 쓰느냐다.C++ 사고가 적은 코드에는항상 공통된 설계 기준이 있다.아래는 현장

cheolystudy.tistory.com

 

사고가 적은 코드에는
항상 같은 질문이 먼저 나온다.

  • 이건 누가 책임지는가
  • 언제까지 유효한가
  • 실패하면 어디서 멈추는가
  • 이 선택을 말로 설명할 수 있는가

이 질문에 답하지 않은 채
코드를 쓰기 시작하면
도구가 아무리 좋아도
결과는 크게 다르지 않다.


도구는 사고를 숨길 뿐, 없애주지 않는다

AI는
빠르게 코드를 만들어준다.

LabVIEW는
구조를 시각적으로 보여준다.

C++은
안전한 장치를 많이 제공한다.

하지만 공통점이 하나 있다.

사고를 대신 책임져주지는 않는다.

오히려 도구가 좋아질수록
사고는 더 늦게,
더 복잡한 형태로 드러난다.

그래서 “편해진 환경”일수록
기준은 더 엄격해져야 한다.


오래 버티는 코드의 특징은 단순하다

지금까지 다룬 모든 언어에서
오래 살아남는 코드는
기술적으로 특별하지 않았다.

대신 이 특징을 공유했다.

  • 구조를 미루지 않는다
  • 불편한 선택을 먼저 한다
  • 책임을 코드에 남긴다
  • 실패를 정상 흐름으로 취급한다

이 습관이 쌓이면
도구가 바뀌어도
사고 빈도는 눈에 띄게 줄어든다.


결국 남는 건 “무엇을 쓸까”가 아니다

AI냐,
LabVIEW냐,
C냐, C++이냐는
중요하지만 본질은 아니다.

시간이 지나고 남는 차이는
항상 여기서 갈린다.

  • 빨리 만들었는가
  • 아니면 설명 가능한 선택을 했는가

사고는
운이 나빠서 나는 게 아니다.
대부분은
미뤄둔 질문이
나중에 돌아온 결과다.

도구는 계속 바뀔 것이다.
하지만 이 질문들은
앞으로도 계속 유효하다.

“이 선택을
나는 설명할 수 있는가?”

반응형
LIST

사고를 부르는 C++ 코드 패턴들

C++
반응형
SMALL

C++ 사고 코드를 보면
대부분 “몰라서” 그렇게 쓴 게 아니다.

  • 바쁘고
  • 일정에 쫓기고
  • 일단 돌아가니까

그 결과로
비슷한 패턴들이 반복해서 쌓인다.

아래는 현장에서 정말 자주 보이는,
그리고 시간이 지나면 반드시 문제를 만드는
C++ 사고 패턴들이다.


1) shared_ptr로 모든 걸 해결하려는 구조

C++ 사고 패턴의 단골 1번이다.

  • 어디서 생성됐는지 모르고
  • 누가 마지막 소유자인지 모르고
  • 왜 살아 있는지도 모른다

shared_ptr 자체가 문제는 아니다.
문제는 공유가 필요한 이유를 설명하지 못하는 구조다.

순환 참조가 생기고,
객체는 사라지지 않고,
메모리는 조용히 새어나간다.

shared_ptr는 설계의 결과여야지
설계의 출발점이 되면 안 된다.


2) 수명보다 인터페이스를 먼저 만든 클래스

인터페이스는 깔끔한데
객체 수명은 아무도 모르는 클래스.

  • 누가 만들고
  • 누가 들고 있고
  • 언제 파괴되는지

이 질문에 답이 없으면
그 클래스는 이미 위험하다.

C++에서 객체는
존재하는 시간 전체가 계약이다.
수명을 설명할 수 없는 객체는
언젠가 반드시 사고를 만든다.


3) 값처럼 보이지만 값이 아닌 타입

C++에서는
값처럼 보이지만 실제로는
무거운 자원을 들고 있는 타입이 많다.

  • 내부에 포인터를 가진 객체
  • 복사 비용이 큰 컨테이너
  • 암묵적으로 공유되는 리소스

이 타입을
아무 생각 없이 복사하고 넘기는 순간
성능 문제나 미묘한 버그가 생긴다.

“이건 그냥 객체니까 괜찮겠지”
이 판단이 사고의 씨앗이다.


4) 예외를 숨긴 라이브러리 경계

라이브러리 경계에서
예외 정책이 불분명하면
사고는 두 배로 커진다.

  • 이 함수는 예외를 던지는가
  • noexcept는 진짜 지켜지는가
  • 호출자는 그걸 알고 있는가

예외가
인터페이스에 드러나지 않으면
호출자는 방어할 수 없다.

C++에서 예외는
편의 기능이 아니라
공개된 계약이어야 한다.


5) 추상화 계층이 너무 얇거나, 너무 두꺼울 때

사고를 부르는 두 극단이다.

  • 너무 얇은 추상화
    → 내부 구현이 그대로 노출됨
  • 너무 두꺼운 추상화
    → 실제 비용과 동작이 보이지 않음

둘 다
디버깅을 어렵게 만든다.

좋은 추상화는
숨길 것과 드러낼 것을
명확히 구분한다.


6) “이건 관례야”로 유지되는 코드

C++ 코드베이스에서
가장 위험한 말 중 하나다.

“원래 이렇게 써.”

  • 문서 없음
  • 타입으로 표현되지 않음
  • 코드에 의도가 남아 있지 않음

이 관례를 아는 사람이 떠나는 순간
코드는 지뢰밭이 된다.

관례로 유지되는 안전은
안전이 아니다.


C++ 사고 패턴의 공통점은 분명하다.

  • 책임이 타입으로 드러나지 않고
  • 수명이 코드에 보이지 않고
  • 의도가 문장으로 설명되지 않는다

이건 언어의 문제가 아니다.
설계에서 피한 질문들이
나중에 사고로 돌아온 것이다.

C++은
잘 쓰면 아주 안전한 언어다.
하지만 그 안전성은
패턴이 아니라 기준에서 나온다.

이 글까지 포함해
C++ 시리즈에서 이야기한 건
결국 하나다.

사고는 우연히 나지 않는다.
항상 반복되는 선택의 결과다.

반응형
LIST

사고를 줄이는 C++ 설계 기준

C++
반응형
SMALL

C++에서 사고를 줄이는 방법은
새로운 문법을 더 배우는 데 있지 않다.

이미 대부분 알고 있다.
문제는 그걸 기준으로 쓰느냐다.

C++ 사고가 적은 코드에는
항상 공통된 설계 기준이 있다.
아래는 현장에서 실제로 효과가 있었던 기준들이다.


1) 소유권을 먼저 설계하고 코드를 쓴다

C++에서 가장 먼저 정해야 할 건
클래스도, 인터페이스도 아니다.

누가 소유자인가

  • 이 자원의 소유자는 누구인가
  • 공유가 필요한가
  • 이전이 가능한가

이 질문에 답이 나오면
스마트 포인터 선택은 자동이다.

  • 단일 소유 → unique_ptr
  • 명시적 공유 → shared_ptr
  • 소유권 없음 → 참조 또는 raw pointer

소유권이 정리되지 않은 상태에서
스마트 포인터를 쓰면
delete는 사라지고, 혼란만 남는다.


2) 수명은 “생성보다 소멸”을 기준으로 본다

C++ 사고의 절반은
소멸 시점에서 발생한다.

  • 소멸 순서 의존
  • 이미 정리된 자원 접근
  • 종료 시점 크래시

그래서 클래스 설계 시
항상 이 질문이 따라야 한다.

이 객체는 언제 사라지는가

생성자는 눈에 띄지만
소멸자는 조용하다.
조용한 지점일수록
의도를 더 분명히 해야 한다.


3) RAII를 형식이 아니라 원칙으로 쓴다

RAII는 문법이 아니다.
습관이다.

  • 리소스 획득 = 객체 생성
  • 리소스 해제 = 객체 소멸

이 원칙이 깨지는 순간
코드는 다시 C처럼 위험해진다.

  • init / release 함수 쌍
  • 예외 경로에서 누락된 정리
  • 조건부 해제 로직

RAII는
예외가 있어도 안전하게 만들기 위한 약속이다.
형식만 흉내 내면 효과가 없다.


4) 예외는 “편의”가 아니라 “계약”으로 다룬다

C++ 예외는 강력하지만
흐름을 숨긴다.

그래서 예외를 쓰는 코드에는
명확한 기준이 필요하다.

  • 어디서 던질 수 있는지
  • 어디까지 전파되는지
  • 누가 처리 책임을 지는지

이게 문서나 인터페이스에 드러나지 않으면
예외는 사고를 줄이지 못한다.

예외를 쓰는 순간
코드는 더 명시적이어야 한다.


5) 추상화 뒤에 비용을 숨기지 않는다

C++은 추상화가 잘 된다.
그래서 비용도 잘 숨겨진다.

  • 복사 비용
  • 동기화 비용
  • 메모리 할당

이 비용을 감춘 채
인터페이스만 깔끔하게 만들면
성능 사고는 시간문제다.

좋은 추상화는
사용하기 쉬운 동시에
비용을 예측 가능하게 만든다.


6) “이건 안전하다”는 말을 금지한다

사고가 적은 팀에는
공통 규칙이 하나 있다.

“이 코드는 안전하다”라는 말을 쓰지 않는다.

대신 이렇게 묻는다.

  • 어떤 조건에서 깨질 수 있는가
  • 어떤 전제를 깔고 있는가
  • 그 전제가 바뀌면 어떻게 되는가

안전하다는 말이 나오는 순간
의심은 멈춘다.
의심이 멈추면
사고는 시작된다.


C++에서 사고를 줄이는 건
테크닉의 문제가 아니다.

  • 소유권을 먼저 정하고
  • 수명을 설계하고
  • 흐름을 숨기지 않는 것

이 기준을
매번 지키는 사람이
결국 가장 적게 고친다.

C++은 안전한 언어가 될 수 있다.
하지만 그 안전성은
작성자의 기준 위에서만 작동한다.

다음 글에서는
이 기준들이 실제 코드에서
어떻게 형태로 드러나는지,
C++에서 특히 조심해야 할 패턴들을
조금 더 구체적으로 살펴보려 한다.

반응형
LIST

C++인데도 사고가 나는 이유는 여전히 같다

C++
반응형
SMALL

C++은 C보다 안전한 언어다.
RAII, 스마트 포인터, 표준 라이브러리.
이론만 보면 사고가 날 이유가 없어 보인다.

그런데 현실은 다르다.

C++ 프로젝트에서도
메모리 문제, 크래시, 알 수 없는 동작은
여전히 반복된다.

이유는 간단하다.
언어는 바뀌었지만
사고방식은 그대로이기 때문이다.


C++ 사고 코드를 보면
대부분 이런 전제를 깔고 시작한다.

“C++이니까 알아서 안전하겠지.”

이 기대가
사고의 출발점이다.


1) 스마트 포인터를 쓰면 안전하다고 믿을 때

C++ 사고의 대표적인 장면이다.

  • shared_ptr 남발
  • 수명 관계를 설명할 수 없음
  • 누가 소유자인지 아무도 모름

스마트 포인터는
메모리를 대신 관리해주지만,
의도를 대신 정해주지는 않는다.

소유권이 불분명한 구조에서는
delete가 사라진 대신
복잡한 버그가 남는다.


2) 객체의 수명을 설계하지 않은 클래스

C++에서 객체는
생성보다 소멸이 중요하다.

  • 생성자는 눈에 띄고
  • 소멸자는 조용히 호출된다

그래서 많은 사고가
소멸 시점에서 발생한다.

  • 이미 해제된 자원 접근
  • 종료 순서에 따른 미묘한 버그
  • 전역 객체 정리 문제

객체의 수명을 설명할 수 없다면
그 클래스는 아직 위험하다.


3) 추상화가 책임을 숨길 때

C++은 추상화가 강력한 언어다.
그래서 사고도 더 교묘해진다.

  • 인터페이스 뒤에 숨은 무거운 동작
  • 이름만 보면 가벼워 보이는 함수
  • 호출 비용이 감춰진 구조

코드가 읽기 쉬워진 대신
실제로 무슨 일이 벌어지는지는
보이지 않게 된다.

사고는
보이지 않는 비용에서 터진다.


4) 예외 처리를 믿고 흐름을 설계할 때

예외는 편리하다.
하지만 예외는 흐름을 숨긴다.

  • 어디서 던졌는지
  • 어디까지 전파되는지
  • 정리 코드는 호출됐는지

이게 명확하지 않으면
예외는 안전장치가 아니라
사고 증폭기가 된다.

C++에서 예외를 쓰는 순간
흐름 설계는 더 엄격해져야 한다.


5) “이건 C 스타일 코드라서”라는 방치

많은 C++ 코드베이스에는
C 스타일 코드가 공존한다.

  • raw pointer
  • 수동 메모리 관리
  • 암묵적 규약

문제는
이 코드가
“어쩔 수 없는 영역”으로 방치된다는 점이다.

C 스타일을 쓴다면
C만큼 엄격해야 한다.
그렇지 않으면
가장 위험한 조합이 된다.


C++ 사고의 본질은
언어의 한계가 아니다.

  • 책임을 명확히 하지 않고
  • 수명을 설계하지 않고
  • 흐름을 숨긴 채 믿어버리는 것

이 습관이
C에서도 사고를 만들고,
C++에서도 그대로 사고를 만든다.

C++은 더 안전해질 수 있는 언어다.
하지만 그 안전성은
작성자의 태도 위에서만 작동한다.

다음 글에서는
이 사고를 줄이기 위해
C++에서 반드시 지켜야 할 기준들을
조금 더 구체적으로 정리해보려 한다.

C와 다른 점,
그리고 여전히 같은 점을 함께.

반응형
LIST

사고를 줄이는 C 코드의 기준과 습관

C언어
반응형
SMALL

C를 오래 쓰는 사람들의 코드를 보면
문법이 특별히 화려하지는 않다.
대신 공통적으로 느껴지는 게 있다.

“이 코드는 함부로 터지지 않겠다.”

그 차이는 재능이 아니라
기준과 습관에서 나온다.


C에서 사고를 줄이는 방법은
의외로 복잡하지 않다.
대신 일관되어야 한다.


1) 메모리의 주인을 항상 명확히 한다

C 코드에서 가장 먼저 정리해야 할 건
포인터가 아니라 책임이다.

  • 누가 할당하는가
  • 누가 해제하는가
  • 언제까지 유효한가

이 세 가지 중
하나라도 흐릿하면
그 코드는 이미 위험하다.

좋은 C 코드는
메모리 사용법을 외우지 않아도
흐름만 읽어도 책임이 보인다.


2) 실패 경로를 먼저 설계한다

사고를 줄이는 사람들은
정상 흐름보다
실패 흐름을 먼저 생각한다.

  • 할당 실패 시
  • 입력이 비었을 때
  • 외부 호출이 끊겼을 때

이때 중요한 건
“어떻게 복구할 것인가”보다
“어디서 멈출 것인가”다.

실패를 감지했는데도
계속 진행하는 코드가
가장 많은 사고를 만든다.


3) 함수 하나를 작은 계약으로 다룬다

C 함수는 단순한 코드 묶음이 아니다.
작은 계약이다.

좋은 함수는
시그니처만 보고도 이게 드러난다.

  • 무엇을 기대하는지
  • 무엇을 보장하는지
  • 실패하면 어떤 상태인지

이게 코드나 주석으로 드러나지 않으면
그 함수는 언젠가 오해를 낳는다.


4) 경계 조건은 옵션이 아니라 기본값이다

사고가 적은 C 코드는
항상 가장 귀찮은 입력부터 생각한다.

  • 길이가 0인 데이터
  • 최대 길이를 넘는 입력
  • NULL이 들어오는 경우

이걸 “나중에 추가”하지 않는다.
처음부터 포함시킨다.

경계 조건을 기본값으로 두는 순간
코드는 훨씬 단단해진다.


5) 정리 코드를 작성하는 데 시간을 아끼지 않는다

C에서 종료 코드는
대충 쓰기 가장 쉽고,
사고가 나기 가장 쉬운 부분이다.

  • 리소스 해제 순서
  • 중복 해제 방지
  • 부분 실패 후 정리

이걸 꼼꼼히 작성하는 사람은
운영 환경에서 훨씬 덜 고생한다.

“어차피 끝나는 코드”라는 생각이
가장 많은 버그를 숨긴다.


6) 테스트는 신뢰가 아니라 의심이다

C 테스트의 목적은
“잘 된다”를 확인하는 게 아니다.

  • 어디서 깨지는지
  • 얼마나 버티는지
  • 실패하면 어떤 모양인지

이걸 확인하는 게 목적이다.

테스트하지 않은 경로는
안전한 게 아니라
아직 사고가 안 난 상태일 뿐이다.


C를 안전하게 쓰는 비결은
새로운 기법에 있지 않다.

  • 책임을 흐리지 않고
  • 실패를 가볍게 넘기지 않고
  • 경계를 처음부터 포함시키는 것

이 기준을
매번 지키는 사람의 코드는
시간이 지나도 크게 변하지 않는다.

C는 솔직한 언어다.
작성자가 준비한 만큼만
정확하게 동작한다.

그래서 C를 잘 쓰는 사람은
문법보다 먼저
자기 기준을 단단히 세운다.

반응형
LIST

현장에서 가장 많이 터지는 C 사고 패턴들

C언어
반응형
SMALL

C로 사고가 났다는 얘기를 들으면
원인은 매번 새로워 보이지만,
코드를 열어보면 장면은 거의 비슷하다.

놀라운 건
이 사고들이 대부분
“어려운 문제”에서 나오지 않는다는 점이다.

아주 사소해 보이는 선택들이
시간이 지나며 문제를 키운다.


1) 수명 관리가 보이지 않는 포인터

가장 흔한 패턴이다.

  • 포인터는 살아 있는데
  • 가리키는 대상은 이미 사라진 상태

이 상태의 코드는
당장 터지지 않는다.
그래서 더 위험하다.

테스트 환경에서는 멀쩡하다가
운영 중, 특정 타이밍에만
의미 없는 값이나 크래시로 돌아온다.

이 사고의 공통점은 하나다.

“이 포인터가 언제까지 유효한지
아무도 설명하지 못한다.”


2) 크기를 믿고 복사하는 코드

C 사고의 단골 손님이다.

  • 문자열 길이를 믿고
  • 배열 크기를 기억에 의존하고
  • 입력이 설계대로 들어올 거라 기대한다

문제는
그 기대를 깨는 건
항상 외부라는 점이다.

파일, 네트워크, 사용자 입력.
이 중 하나만 흔들려도
버퍼는 바로 넘어간다.

이 패턴의 무서운 점은
“지금까지 문제 없었다”는 말이
아무 의미도 없다는 것이다.


3) 실패했는데도 계속 진행하는 흐름

많은 C 코드가
실패를 감지하지만
그걸 멈출 이유로 쓰지 않는다.

  • malloc 실패
  • 파일 오픈 실패
  • 장비 응답 실패

에러 코드는 반환되는데
호출자는 그냥 다음 줄로 간다.

이 구조에서 사고는
즉시 터지지 않는다.
조금 더 진행한 뒤,
전혀 상관없는 지점에서 터진다.

그래서 디버깅은 더 어려워진다.


4) 계약이 없는 함수 인터페이스

현장에서 자주 보는 함수 시그니처다.

  • 입력 범위가 불분명하고
  • 반환값의 의미가 모호하고
  • 실패 시 상태가 정의돼 있지 않다

이런 함수는
사용하는 쪽에서
항상 암묵적인 전제를 깔게 만든다.

그 전제가 하나라도 어긋나는 순간
사고는 자연스럽게 발생한다.

C에서 함수는
코드 조각이 아니라 계약이다.
이 계약이 문서나 시그니처로 드러나지 않으면
사고는 시간문제다.


5) 정리 순서를 고려하지 않은 종료 코드

사고는 시작보다
종료에서 더 많이 난다.

  • 리소스 해제 순서
  • 스레드 종료 타이밍
  • 전역 객체 정리

종료 코드가
“어차피 끝나는 거니까”라는 이유로
대충 작성되면
희한한 버그가 생긴다.

특히
프로그램 종료 시점에만 터지는 버그는
재현도 어렵고, 로그도 부족하다.


6) 테스트가 아니라 운에 맡긴 코드

마지막 패턴은
사고라기보다 태도에 가깝다.

  • 특정 입력만 테스트
  • 정상 흐름만 확인
  • 에러 경로는 직접 안 타봄

C는
운이 좋으면 오래 버틴다.
하지만 운은
언젠가 반드시 떨어진다.

테스트하지 않은 경로는
아직 사고가 안 났을 뿐,
안전한 게 아니다.


C 사고 패턴을 보면
공통점이 하나로 모인다.

  • 책임이 보이지 않고
  • 경계가 흐리고
  • 실패를 가볍게 여긴다

이건 문법 문제가 아니다.
사고를 대하는 태도의 문제다.

다음 글에서는
이 사고 패턴들을
어떻게 코드 수준에서 차단할 수 있는지,
C를 조금 더 안전하게 쓰는 기준들을
정리해보려 한다.

도구가 아니라
습관의 이야기다.

반응형
LIST

C에서 아직도 사고 나는 이유는 문법이 아니다

C언어
반응형
SMALL

C는 오래된 언어다.
문법도 단순하고, 참고 자료도 넘쳐난다.
그런데 이상하게도 C로 만든 프로그램에서는
여전히 비슷한 사고가 반복된다.

널 포인터, 메모리 오염, 예기치 않은 크래시.
겉으로 보면 문법 실수 같지만,
조금만 깊이 들어가 보면 원인은 거의 항상 같다.

문제가 되는 건 문법이 아니라
사고방식이다.


C로 사고가 나는 순간을 보면
대부분 이런 흐름을 따른다.

“이 정도는 괜찮겠지.”

이 한 문장이
수많은 장애의 출발점이다.


1) 메모리를 ‘값’처럼 다루기 시작할 때

C에서 메모리는 값이 아니다.
상태이고, 계약이고, 책임이다.

하지만 사고가 나는 코드를 보면
메모리를 마치 int 변수처럼 다룬다.

  • 누가 할당했는지 모르고
  • 누가 해제하는지 신경 쓰지 않고
  • 언제까지 유효한지도 고려하지 않는다

이 상태에서 프로그램이
정상 동작하는 게 오히려 이상하다.

메모리를 다룰 때는
항상 이 질문이 먼저 와야 한다.

이 메모리는 누가 책임지는가?


2) “지금은 안 터지니까”라는 판단

C 코드에서 가장 위험한 말은
“지금은 괜찮다”다.

  • 입력이 항상 정상일 거라는 가정
  • 배열 크기를 넘지 않을 거라는 기대
  • 호출 순서가 바뀌지 않을 거라는 믿음

이 가정들은
언젠가 반드시 깨진다.

C는 그 순간을
아주 잔인하게 알려준다.


3) 경계 조건을 마지막에 생각할 때

많은 C 코드가
정상 흐름부터 작성된다.

  • 정상 입력
  • 정상 호출
  • 정상 종료

문제는
현실에서 정상 흐름은
가장 드물다는 점이다.

  • 길이가 0인 데이터
  • 예상보다 큰 입력
  • 중간에 끊긴 처리

이 경계 조건을
나중에 붙이는 순간
코드는 이미 위험해져 있다.


4) 책임이 보이지 않는 함수 설계

좋지 않은 C 함수의 공통점은
사용법을 외우지 않으면 쓸 수 없다는 점이다.

  • 이 함수 호출 전에 뭐 해야 하지?
  • 호출 후에 뭘 해제해야 하지?
  • 실패하면 어디까지 처리된 거지?

함수 시그니처만 보고
이 질문에 답할 수 없다면
그 함수는 이미 사고 후보군이다.

C에서는
함수 하나가 작은 계약이다.
이 계약이 불명확할수록
사고 확률은 기하급수적으로 올라간다.


5) “C니까 어쩔 수 없다”는 포기

사고가 반복되는 코드에서
자주 나오는 말이 있다.

“C는 원래 위험한 언어잖아.”

이 말이 나오는 순간
개선은 멈춘다.

C가 위험한 건 사실이다.
하지만 그 위험을
어디까지 통제할지는
전적으로 작성자의 선택이다.

  • 방어적인 코드
  • 명확한 책임
  • 보수적인 가정

이 세 가지만 지켜도
사고의 절반 이상은 사라진다.


C에서 사고가 나는 이유는
언어가 낡아서가 아니다.

  • 가정을 너무 많이 믿고
  • 책임을 흐리게 두고
  • 경계를 나중에 생각하기 때문이다.

C는 냉정한 언어다.
작성자가 생각한 만큼만
정확하게 동작한다.

그래서 C를 잘 쓰는 사람은
문법보다 먼저
사고방식을 단련한다.

다음 글에서는
이 사고가 실제로 어떤 형태로 터지는지,
현장에서 자주 보는 C 사고 패턴들을
조금 더 구체적으로 정리해보려 한다.

반응형
LIST