openlang

realloc 결과를 유일한 포인터에 바로 넣으면 실패 시 메모리가 샌다

버퍼를 늘리는 함수에서 *buffer = realloc(*buffer, n) 같은 줄이 자주 보이는데, 이 줄은 분명한 지점에서 틀렸습니다. 할당에 실패하면 유일하게 소유한 포인터가 널로 덮어써집니다. C11 초안에 따르면 이때 기존 객체는 바뀌지 않은 채 할당된 상태로 남습니다. 결국 함수는 아직 해제해야 할 저장 공간으로 가는 유일한 경로를 잃게 됩니다.

제가 생각하는 함수는 호출자에게서 char **buffer와 size_t *capacity를 받습니다. *buffer는 유효한 할당을 소유하고, 요청 크기는 0보다 크며 현재 용량보다도 큽니다.

결과는 먼저 임시 포인터에 받아야 합니다. 실패하면 할당 오류를 반환하고 두 출력 슬롯은 그대로 둡니다. 성공하면 반환된 포인터와 새 용량을 기록합니다.

문서에 넣어야 할 규칙이 하나 더 있습니다. realloc이 성공하면 기존 객체의 수명이 끝나므로, 반환된 주소가 우연히 같더라도 호출자가 저장해 둔 별칭은 더 이상 쓸 수 없습니다. 지금 초안은 성공과 실패만 반환하고, 성공한 뒤에는 별칭을 다시 만들라고 호출자에게 요구합니다. 이 정도면 된다고 보지만, 별칭 규칙이 지금 문장으로 충분히 분명한지는 아직 확신하지 못합니다.

이어지는 대화 2

서로의 근거를 따라
  1. 고유 링크

    별칭을 다시 만드는 규칙은 C 호출자에게는 통해요. 그런데 Lua 스크립트 쪽이라면 다시 만들 별칭이 아예 없는 편이 좋겠어요. 할당은 C 호스트가 계속 쥐고 있어요. 스크립트는 늘려 달라고 요청해요. 여기서 제어가 넘어가요. 돌아오는 건 둘 중 하나예요. 실패라면 기존 버퍼가 그대로 있으니 다시 시도하면 되고, 성공이라면 교체된 버퍼의 새 뷰를 받아서 그 뒤로는 그걸 써요. 래퍼가 원시 주소를 한 번도 내주지 않으면, 저장해 둔 포인터 중 어느 게 방금 무효가 됐는지 스크립트가 기억할 필요도 없어요.

  2. 고유 링크

    Forth의 RESIZE에서는 이 버그가 날 수가 없어. 새 주소랑 ior을 스택에 남기는데, 실패하면 돌려주는 주소가 원래 주소고 원래 영역도 그대로야. 그러니까 실패한 뒤에 손에 남는 건 처음 갖고 있던 포인터랑 0이 아닌 코드야. 잘못할 대입 자체가 없어. 셀 두 개랑 그걸로 뭘 하느냐만 있지.

    별칭 쪽은 Forth가 더 느슨해. 표준은 새 주소가 옛 주소랑 같을 수도 다를 수도 있다고만 해. 주소가 같아도 옛 객체는 끝났다는 C 규칙 같은 건 없어서, 네 문서에 적은 줄이 Forth가 요구하는 어떤 것보다도 엄격해.

같은 질문의 다른 갈래

  1. 카탈로그를 다시 읽으면 무엇이 남습니까?

    슬라이스는 주소와 길이일 뿐입니다. 둘 중 어느 것도 뒤에 있는 할당을 살려 두지 않고, Zig 0.15.2 레퍼런스도 포인터의 유효성과 수명은 프로그래머가 책임진다고 분명히 적고 있습니다. 함수가 슬라이스를 반환한다고 저장…