1P by GN⁺ | ★ favorite | 댓글 1개
  • Linux 7.2에서 커널 내부의 strncpy API 사용처가 사라지며, 오래전부터 폐기 예정이던 문자열 복사 인터페이스가 최종 제거됨
  • strncpy()는 지정한 바이트 수만큼 복사하지만 NUL 종료 동작이 직관적이지 않아, 커널에서 수년간 버그의 원인으로 남아 있었음
  • 목적지 버퍼를 불필요하게 0으로 채우는 특성은 성능 문제까지 만들었고, 이를 걷어내는 데 약 6년과 362개 커밋이 필요했음
  • 금요일 머지에서 API 본체뿐 아니라 마지막 per-CPU 아키텍처별 구현도 함께 제거됨
  • 커널 코드는 이제 용도에 따라 strscpy(), strscpy_pad(), strtomem_pad(), memcpy_and_pad(), memcpy() 같은 대체 함수를 골라야 함

Linux 7.2에서 사라진 strncpy

  • Linux 7.2는 커널에서 오래전부터 폐기 예정이던 strncpy API를 최종 제거함
  • 6년에 걸친 정리 작업 끝에 커널 내부에서 strncpy 인터페이스를 쓰는 코드가 더 이상 남지 않게 됨
  • 이번 변경은 단순한 함수 교체가 아니라, 오래된 문자열 복사 관행을 커널 전반에서 걷어낸 작업에 가까움

제거까지 걸린 작업 규모

  • strncpy 제거에는 약 362개 커밋이 필요했음
  • 작업은 커널 내부의 strncpy 사용 코드를 단계적으로 없애는 방식으로 진행됨
  • Linux 7.2에서 이 정리 작업이 완료 지점에 도달함

strncpy가 커널에서 문제가 된 이유

  • strncpy는 Linux 커널 안에서 수년간 지속적인 버그 원인으로 여겨졌음
  • 특히 두 가지 동작이 문제였음
    • NUL 종료 의미와 동작이 직관적이지 않아 사용자가 실수하기 쉬움
    • 목적지 버퍼를 중복으로 0 채움해 불필요한 성능 비용이 생김

실제 제거 머지

  • 금요일에 이뤄진 머지가 strncpy API를 제거함
  • 같은 머지에서 마지막 per-CPU 아키텍처별 strncpy 구현도 함께 사라짐

커널 코드에서 쓸 대체 API

  • strncpy 대신 복사 대상과 종료 조건에 맞는 함수를 선택해야 함
    • strscpy(): NUL 종료되는 목적지에 사용
    • strscpy_pad(): NUL 종료되는 목적지에 0 패딩이 필요한 경우 사용
    • strtomem_pad(): NUL 종료되지 않는 고정 폭 필드에 사용
    • memcpy_and_pad(): 명시적 패딩이 있는 제한된 복사에 사용
    • memcpy(): 길이를 알고 있는 메모리 복사에 사용

댓글과 토론

Hacker News 의견들
  • 예전에는 세계 최고 수준의 C 개발자라는 Linux 커널 개발자들이 stringbuffer나 stringview 타입을 만들 줄 모른다고 놀리곤 했지만, 지금처럼 이 주제에 대한 합의가 있던 시절은 아니었으니 어느 정도는 이해됨
    제대로 된 방향을 이미 봤던 사람은 Dennis Ritchie였고, 1990년에 C용 팻 포인터 타입을 제안했음. C99에 들어갔다면 완벽한 추가 기능이었을 텐데, 위원회가 그걸 넣었다면 세상이 꽤 달라졌을 수도 있음
    2007년에 Walter Bright의 “C's greatest mistake” 글이 나오면서 두 번째 기회가 있었고, 본질적으로 Ritchie와 같은 아이디어인 슬라이스/stringview를 더 명확하게 설명했지만 C11에도 들어가지 못했음. C23까지 왔는데도 여전히 없고, 대신 _Generic과 VLA는 얻었으니 신나게 파티나 하자는 느낌

    • Walter Bright의 2007년 글은 여기 있음: https://digitalmars.com/articles/C-biggest-mistake.html
      검색하다가 같은 주제의 Reddit 글도 봤는데, 자전거 창고 논쟁이 웃겼음: https://www.reddit.com/r/C_Programming/comments/90uq7c/cs_bi...
      C 배열이 포인터로 붕괴하는 동작이 왜 설계됐는지 궁금함. B 코드를 최소한의 변경으로 C로 컴파일할 수 있게 하려던 목적이었다는 설명이 있는데, B에서는 배열 선언이 실제로 포인터와 배열을 정의하고 그 포인터가 배열의 첫 원소를 가리키도록 초기화됐다고 함
    • VLA는 C11에서 선택 기능으로 강등됐고, 그건 좋은 일이라고 봄
      지금 더 큰 문제는 C 표준 라이브러리가 여전히 K&R 시대에 묶여 있고, C99에 추가된 구조체 인자나 반환값 같은 언어 기능조차 표준 라이브러리 API에 반영되지 않았다는 점임. 표준 라이브러리에 포인터/크기 쌍인 범위 구조체와 그걸 쓰는 새 문자열 함수나 갱신된 문자열 함수만 있어도 꽤 나아질 수 있음
    • Ritchie의 제안 링크: https://web.archive.org/web/20150611114358/https://www.bell-...
    • 팀워크에서 제일 거슬리는 패턴이 이거임. 해법 A, B, C가 있고 각각 장단점이 있어서 2주 동안 토론한 뒤, 결국 아무것도 선택하지 않음
    • 그건 WG14의 우선순위가 어디에 있는지 보여줄 뿐임
  • Linux 커널 안의 strncpy는 직관에 반하는 의미론, NUL 종료 처리, 목적지에 불필요하게 0을 채우는 성능 문제 때문에 몇 년 동안 “끈질긴 버그 원천”이었다고 함
    C 코드 리뷰를 부탁받을 때마다 strncpy를 찾아봤고, 늘 거기서 버그를 발견했음

  • 40년 동안 거슬렸던 것들이 있음. NUL 종료 문자열, 그리고 이제는 입출력에서 UTF-8이 아닌 문자열까지 포함됨
    줄 끝을 LF, CR, CRLF로 처리하는 관습도 그렇고, 파이프나 쉼표로 필드를 구분하는 방식도 그렇다. GS, FS, RS 같은 모호하지 않은 ASCII 문자를 썼다면 줄 끝 인코딩/디코딩은 입출력의 문제가 되고, HT/VT/CR/LF/FF는 말 그대로 출력 관련 코드로 남을 수 있었을 것임

    • ASCII의 필드/레코드 구분 문자로 프레이밍된 데이터를 변환하는 프로젝트를 해봤는데, 정말 쉽게 처리됐음
      쉼표 구분 데이터에서 생기는 지저분한 이스케이프 처리 고민이 사라져서 훨씬 단순해짐
    • Unicode에는 이제 더 많은 선택지도 있음. EBCDIC에서 온 듯한 NL Next line, Unicode가 만든 LS Line separator, PS Paragraph separator가 있음
      Unicode 표준은 CR, LF, CRLF와 위 문자들뿐 아니라 세로 탭폼 피드도 줄 구분자로 처리해야 한다고 말함
    • 표준 입력/출력에서 UTF-8은 완벽하게 잘 동작함. 물론 국제 텍스트 인코딩에서는 90년대 초반에 머물러 있는 Windows가 아니라면 그렇다는 얘기임
      LF, CR, CRLF 같은 줄 끝은 운영체제 관습이기도 하고, 프로그래밍 언어가 올바른 줄 끝을 “추측”하려 들지 않는 편이 더 낫다. 이건 해결하는 것보다 더 많은 문제를 만들고, 다시 말하지만 대체로 Windows 특유의 문제라 Microsoft가 Windows를 현 세기로 데려와야 할 일임
    • LF가 가장 말이 되지만, 텍스트 파일이라면 어느 쪽이든 괜찮음. 문제는 CSV가 텍스트가 아니라는 데 있음
      마지막으로 bash에서 CSV 파일을 다뤄야 했을 때는 내부적으로 RS와 FS로 변환해서 처리했음
    • 그냥 어디서나 UTF-8을 쓰면 된다고 봄
  • strncpy 대신 Linux 커널 코드에서는 NUL 종료 목적지에는 strscpy(), 0 패딩이 필요한 NUL 종료 목적지에는 strscpy_pad(), NUL 종료가 아닌 고정 폭 필드에는 strtomem_pad(), 명시적 패딩이 있는 경계 복사에는 memcpy_and_pad(), 길이를 아는 메모리 복사에는 memcpy()를 쓰라고 함
    이건 악몽 같고, 이렇게까지 복잡해야 하는지 모르겠음

    • 이유는 성능임. 이 대부분을 처리하는 안전한 만능 함수는 내부 분기 때문에 느려질 수밖에 없고, 어떤 함수를 고르느냐에는 개발자의 의도가 담겨 있음
      코드를 읽을 때 함수 선택만으로 개발 의도가 명확히 보이는 편이 더 낫다고 봄
    • strncpy를 제대로 쓰는 건 원래부터 늘 복잡했음
    • 최소한 이름이라도 좀 더 낫게 지을 수는 없었을까 싶음
  • 이런 지루한 반복 작업이야말로 시스템 엔지니어링의 진짜 일이 이루어지는 곳임
    Linux 커널을 전체 과정 내내 실사용 가능하게 유지하면서 더 신뢰성 있게 만드는 이런 대형 인프라 프로젝트는 몇 달이 아니라 수십 년 단위로 움직임

    • 왜 수십 년 규모가 되는지는 이해함. 사용자와 의존성의 긴 꼬리가 정말 길기 때문임
      하지만 그 속도로 장기적인 의미 있는 진전을 만들 수 있는지는 잘 모르겠음. 불평이라기보다는 핵심 인프라의 역설에 가까움
  • 대단하고 겸손해지는 작업임. 이렇게 많은 사람이 기여했다는 게 놀라움
    “멋진 새 기능”은 공로를 인정받기 쉽지만, 커널처럼 근본적인 대상에서는 나쁜 기능을 제거하는 일이 오히려 더 중요할 수도 있음
    50년 뒤 사람들이 소스 코드를 읽는 법을 잊고 Claude/Codex 찌꺼기가 조용히 쌓이며 지구 에너지의 대부분을 태우는 시대가 오면, 이런 일들이 “창건 시대”의 전설처럼 남을 것 같음

    • Vernor Vinge의 Deepness in the Sky가 떠오름. 거기서는 어떤 사람이 소프트웨어 고고학으로 우주선을 유지보수함
      Unix epoch이 뭔지 아는 유일한 사람이기도 함
    • 50년 뒤 모두가 소스 코드를 이해하는 법을 잊지는 않을 것 같음. 사물이 어떻게 작동하는지 알고 싶어 하는 인간의 욕구는 그때도 남아 있을 것임
    • AI가 만든 잡탕 코드는 그보다 훨씬 전에 감당 불가능해질 거라고 봄
  • 0 종료 문자열은 컴퓨팅 역사상 가장 큰 실수라고 생각함. Pascal식 문자열이 훨씬 안전했음

    • Visual Basic, 그리고 나중의 COM이 택한 BSTR 같은 중간 지점도 있음
      여전히 0으로 끝나는 문자 배열을 가리키는 포인터지만, 포인터가 가리키는 첫 바이트 바로 앞에 길이 필드가 있음. 내장 NUL 문자가 없다는 가정하에서는 C 문자열과도 호환되고, BSTR 타입 함수는 길이 값을 활용할 수 있음
    • 어느 정도 동의하지만 크기 필드의 자료형을 두고 다툼이 있었을 것임. 가변 길이가 아니었다면 더 그랬고, 가변 길이라면 또 다른 문제가 있었을 것임
      한동안은 16비트조차 너무 과하다고 느꼈을 수 있고, 지금은 32비트가 너무 작아 보일 수 있음. “강한 타입” 언어라는 C는 정작 중요했던 곳에서는 꽤 느슨함
    • 0 종료 문자열은 엄청나게 많은 유용한 소프트웨어의 기반이었음. 컴퓨팅 최대의 실수라고 부르는 건 좀 과장임
      Pascal 관련 코드는 30년 넘게 안 짰지만, 당시에도 문자열 시스템이 너무 쓰기 어렵다고 생각했던 희미한 기억이 있음
    • 255자면 모두에게 충분해야 하는 거 아니었나?
    • 줄바꿈 종료 라인만큼이나 나쁨
  • 문자열 자료형 하나 없어서 겪는 고통과 삽질이 너무 많음

    • 정확히는 문자열 자료형이 없어서가 아니라, C에 문자열 자료형이 없다는 사실을 우회하느라 생기는 고통과 삽질임
    • 여기서 강한 자료형을 도입하려면 어떤 방식이 가능할까? strncpy 주변 코드도 그 타입과 함수들을 쓰도록 대규모 리팩터링해야 하지 않을까 싶음
  • strncpy 사용처를 다시 쓰는 데 무엇이 그렇게 어려워서 6년이나 걸렸는지 궁금함
    사용처가 그만큼 넓었는지, 아니면 같은 파일을 건드릴 일이 있을 때만 바꾸는 장기 작업이었는지, 혹은 다른 어려움이 있었는지 알고 싶음

  • Win32 앱에서 공백 패딩 문자열을 쓰는 코드를 다룬 적이 있음. 목적지 문자열은 공백으로 패딩되지만 마지막 바이트에는 여전히 널 문자가 있었음
    길이, 복사 같은 작업에는 전용 문자열 함수 버전을 써야 했음. 왜 그랬는지는 모르겠지만, 코드베이스가 워낙 오래돼서 Pascal 구조체 동작에서 기원했을 수도 있음

    • SQL 데이터베이스의 char 필드에서 온 문자열이라 그랬을 수도 있음. varchar가 아니라 char 필드는 공백으로 패딩됨
    • 이 동작의 뿌리는 Pascal이 아니라 COBOL일 것 같음
    • 문자열 크기가 바뀔 때 재할당을 막으려던 것일 수도 있고, CPU 캐시 라인 정렬 때문일 수도 있음