Meltdown·Spectre를 계기로 C가 더 이상 ‘금속에 가까운’ 언어가 아님을 짚고, 현대 CPU와 컴파일러가 C의 추상 기계를 흉내 내느라 치르는 대가, 최적화와 메모리 모델의 복잡성, 그리고 더 빠른 하드웨어를 위한 대안적 프로그래밍 모델과 인터페이스를 논의한다.
URL: https://queue.acm.org/detail.cfm?id=3212479
Title: C는 저수준 언어가 아니다
2018년 4월 30일
최근 Meltdown과 Spectre 취약점 이후, 근본 원인을 들여다볼 가치가 있다. 두 취약점 모두 프로세서가 어떤 접근 검사 이후의 명령들을 추측 실행하고, 그 결과를 사이드 채널을 통해 공격자가 관찰할 수 있게 한 문제였다. 그 외 여러 기능들과 함께 이러한 취약점을 낳은 기능들은, C 프로그래머들이 자신들이 저수준 언어로 프로그래밍하고 있다는 믿음을 계속 유지하게 하기 위해 추가되었다. 하지만 이는 수십 년 전부터 사실이 아니었다.
프로세서 벤더만의 문제가 아니다. C/C++ 컴파일러를 만드는 우리도 이 과정에 동참해 왔다.
컴퓨터 과학의 선구자 Alan Perlis는 저수준 언어를 이렇게 정의했다:
"프로그래밍 언어가 저수준이라는 것은, 그 언어의 프로그램이 무관한 것들에 주의를 기울여야 함을 의미한다."5
이 정의는 C에도 적용되지만, 사람들이 저수준 언어에서 원하는 바를 포착하지는 못한다. 사람들이 언어를 저수준이라 여기는 데에는 여러 속성이 작용한다. 프로그래밍 언어를 연속체로 생각해 보자. 한쪽 끝에는 어셈블리가, 다른 끝에는 스타십 엔터프라이즈의 컴퓨터 인터페이스가 있다. 저수준 언어는 "하드웨어에 가깝고", 고수준 언어는 인간의 사고방식에 더 가깝다.
언어가 "하드웨어에 가깝다"고 하려면, 대상 플랫폼이 노출하는 추상화에 쉽게 매핑되는 추상 기계를 제공해야 한다. C가 PDP-11에 대해서는 저수준 언어였다고 주장하기는 쉽다. 둘 다 프로그램이 순차적으로 실행되고, 메모리가 평평한(플랫) 공간이며, 심지어 전위/후위 증가 연산자가 PDP-11의 주소 지정 모드와 깔끔하게 들어맞는 모델을 설명했다.
Spectre와 Meltdown의 근본 원인은, 프로세서 설계자들이 단지 빠른 프로세서가 아니라, PDP-11과 같은 추상 기계를 노출하는 빠른 프로세서를 만들려고 했기 때문이다. 이는 C 프로그래머들이 자신의 언어가 기반 하드웨어에 가깝다고 믿게 해 주기 때문에 필수적이다.
C 코드는 대체로 직렬(순차) 추상 기계를 제공한다(C11 전까지는, 비표준 벤더 확장을 제외하면 전적으로 직렬). 스레드 생성을 라이브러리 연산으로 제공하며 비싸다고 알려져 있기 때문에, C 코드를 실행 유닛에 바쁘게 채우고 싶은 프로세서는 ILP(명령어 수준 병렬성)에 의존한다. 인접한 연산들을 살펴 독립적인 것들을 병렬로 발행(issue)한다. 이는 프로그래머들이 대부분 순차적인 코드를 쓰도록 허용하기 위해 상당한 복잡성(과 전력 소모)을 추가한다. 반대로 GPU는 이러한 논리 없이도 매우 높은 성능을 달성하지만, 그 대가로 명시적으로 병렬적인 프로그램을 요구한다.
높은 ILP 추구는 Spectre와 Meltdown의 직접적인 원인이었다. 최신 인텔 프로세서는 한 번에 최대 180개의 명령을 비행 중(in flight)으로 갖는다(각 연산이 끝난 뒤에야 다음 연산이 시작된다고 기대하는 C의 순차적 추상 기계와 극명히 대조된다). C 코드에 대한 일반적 휴리스틱은 평균적으로 7개마다 분기가 하나 있다는 것이다. 단일 스레드에서 이런 파이프라인을 가득 유지하려면, 다음 25개의 분기 목표를 추측해야 한다. 이는 다시 복잡성을 더하고, 잘못된 추측은 작업을 수행한 뒤 폐기하게 만들어 전력 소모에도 좋지 않다. 이 폐기된 작업은 가시적인 부작용을 남기며, 바로 Spectre와 Meltdown 공격이 이를 악용했다.
최신 하이엔드 코어에서 레지스터 리네임 엔진은 다이 면적과 전력의 가장 큰 소비자 중 하나다. 더 나쁜 점은, 어떤 명령이 실행되는 동안에는 이를 끌 수 없고 전력 게이팅도 할 수 없다는 점이다. 트랜지스터는 싸지만 전력이 들어간 트랜지스터는 비싼 자원인 다크 실리콘 시대에는 특히 불편하다. 이 유닛은 GPU에는 눈에 띄게 없다. 여기서의 병렬성은 본질적으로 스칼라인 코드에서 명령어 수준 병렬성을 추출하려는 게 아니라 다수의 스레드에서 오기 때문이다. 명령들이 재정렬되어야 할 의존성이 없다면, 레지스터 리네이밍은 필요 없다.
C 추상 기계의 메모리 모델의 또 다른 핵심 부분을 생각해 보자. 평평한 메모리. 이는 20년이 넘도록 사실이 아니었다. 최신 프로세서에는 레지스터와 주 메모리 사이에 대개 3단계의 캐시가 있어 지연을 감추려 한다.
캐시는 그 이름이 암시하듯 프로그래머에게 숨겨져 있으며, 따라서 C에서는 보이지 않는다. 캐시를 효율적으로 사용하는 것은 최신 프로세서에서 코드를 빠르게 만드는 가장 중요한 방법 중 하나지만, 이는 추상 기계에 완전히 숨겨져 있다. 그래서 프로그래머는 효율적인 코드를 작성하려면 캐시의 구현 세부(예를 들어, 64바이트 경계 정렬된 두 값이 같은 캐시 라인에 들어갈 수 있다는 점)을 알아야 한다.
저수준 언어에 흔히 부여되는 속성 중 하나는 빠르다는 것이다. 특히 복잡하지 않은 컴파일러로도 빠른 코드로 쉽게 변환되어야 한다. 충분히 똑똑한 컴파일러가 언어를 빠르게 만들 수 있다는 주장은, 다른 언어를 이야기할 때 C 옹호자들이 흔히 일축하는 주장이다.
안타깝게도, 단순한 번역만으로 빠른 코드를 제공한다는 것은 C에 대해 사실이 아니다. 프로세서 설계자들이 C 코드를 빠르게 실행할 수 있는 칩을 만들려고 영웅적인 노력을 기울였음에도, C 프로그래머들이 기대하는 수준의 성능은 믿을 수 없을 정도로 복잡한 컴파일러 변환의 결과로만 달성된다. LLVM의 관련 부분을 포함한 Clang 컴파일러는 약 200만 줄의 코드다. C를 빠르게 만들기 위해 필요한 분석 및 변환 패스만 세어도 거의 20만 줄(주석과 공백 제외)에 달한다.
예를 들어 C에서 많은 데이터를 처리하려면 각 원소를 순차적으로 처리하는 루프를 작성한다. 이를 최신 CPU에서 최적으로 실행하려면, 컴파일러는 먼저 루프 반복이 서로 독립적이라는 것을 판정해야 한다. C의 restrict 키워드는 여기에 도움이 된다. 이는 한 포인터를 통한 쓰기가 다른 포인터를 통한 읽기와 간섭하지 않음을 보장한다(혹은 간섭하더라도, 프로그램이 예상치 못한 결과를 내도 프로그래머가 괜찮다는 뜻). 이 정보는 Fortran과 같은 언어에 비해 훨씬 제한적이며, 이것이 C가 HPC(고성능 컴퓨팅)에서 Fortran을 대체하지 못한 큰 이유의 일부다.
컴파일러가 루프 반복이 독립적임을 판정하면, 다음 단계는 결과를 벡터화하려는 시도다. 최신 프로세서는 스칼라 코드보다 벡터 코드에서 4~8배의 처리량을 얻기 때문이다. 이런 프로세서에 대한 저수준 언어라면 임의 길이의 네이티브 벡터 타입을 제공해야 한다. LLVM IR(중간 표현)은 정확히 이것을 제공한다. 큰 벡터 연산을 작은 것으로 나누는 것이 더 큰 벡터 연산을 구성하는 것보다 언제나 쉽기 때문이다.
이 지점에서 최적화기는 C의 메모리 배치 보장과 싸워야 한다. C는 같은 접두(prefix)를 가진 구조체를 상호 교환하여 사용할 수 있음을 보장하고, 구조체 필드의 오프셋을 언어 차원에서 노출한다. 이는 컴파일러가 필드를 재배열하거나 패딩을 삽입해 벡터화를 개선할 자유가 없음을 의미한다(예를 들어, 구조체의 배열을 배열의 구조체로 변환하거나 그 반대로). 이는 저수준 언어에서는 문제가 아닐 수 있다. 데이터 구조 배치에 대한 미세 제어는 기능이기 때문이다. 그러나 C를 빠르게 만들기는 더 어렵게 한다.
C는 또한 배열에는 패딩이 없음을 보장하므로, 구조체 끝에 패딩을 요구한다. 패딩은 C 명세에서 특히 복잡한 부분이며 언어의 다른 부분과도 좋지 않게 상호작용한다. 예를 들어 두 struct를 타입을 신경 쓰지 않는 비교(예: memcmp)로 비교할 수 있어야 하므로, struct의 복사본은 패딩을 유지해야 한다. 몇몇 실험에서, 일부 작업 부하에서 총 실행 시간 중 눈에 띄는 비율이 패딩 복사에 쓰인다는 것이 발견되었다(대개 난잡한 크기와 정렬을 가진다).
C 컴파일러가 수행하는 핵심 최적화 두 가지를 생각해 보자: SROA(집합의 스칼라 치환)와 루프 언스위칭(loop unswitching). SROA는 struct(그리고 고정 길이의 배열)를 개별 변수들로 치환하려 한다. 그러면 컴파일러는 접근을 독립적으로 취급할 수 있고, 결과가 결코 가시적이지 않음을 입증할 수 있으면 연산을 완전히 생략할 수 있다. 이는 어떤 경우에는 패딩을 삭제하는 부수효과가 있지만, 어떤 경우에는 그렇지 않다.
두 번째 최적화인 루프 언스위칭은 조건을 포함하는 루프를, 양쪽 경로에 루프가 있는 조건문으로 변환한다. 이는 흐름 제어를 바꾸며, 저수준 언어 코드가 실행될 때 프로그래머가 어떤 코드가 실행될지 안다는 생각에 모순된다. 또한 C의 미지정 값(unspecified value)과 정의되지 않은 동작(undefined behavior) 개념과 상당한 문제를 야기할 수 있다.
C에서는 초기화되지 않은 변수에서의 읽기가 미지정 값이며, 읽을 때마다 임의의 값이 될 수 있다. 이는 중요하다. 예컨대 페이지의 지연(lazy) 재활용 같은 동작을 가능하게 하기 때문이다. 예를 들어 FreeBSD의 malloc 구현은 페이지가 현재 사용되지 않음을 운영체제에 알리고, 운영체제는 페이지에 대한 첫 쓰기를 더 이상 그렇지 않다는 힌트로 사용한다. 새로 malloc된 메모리에 대한 읽기는 처음에는 이전 값을 읽을 수 있다. 그런 다음 운영체제가 기본 물리 페이지를 재사용할 수 있고, 이어서 페이지의 다른 위치에 대한 다음 쓰기에서 새로 0으로 지워진 페이지로 바꿀 수 있다. 그러면 같은 위치에 대한 두 번째 읽기는 0 값을 줄 것이다.
만약 흐름 제어에 미지정 값을 사용한다면(예: 이를 if 문의 조건으로 사용), 결과는 정의되지 않은 동작이 된다: 무엇이든 일어날 수 있다. 이제 루프 언스위칭 최적화를, 이번에는 루프가 0회 실행되는 경우를 생각해 보자. 원래 버전에서는 루프 본문 전체가 데드 코드다. 언스위칭된 버전에서는 이제 그 변수에 대한 분기가 생기는데, 그 변수는 초기화되지 않았을 수 있다. 일부 데드 코드가 이제 정의되지 않은 동작으로 변환되었다. 이는 C 의미론을 면밀히 조사해 보면 건전하지 않음이 드러나는 많은 최적화 중 하나일 뿐이다.
요약하면, C 코드를 빠르게 실행시키는 것은 가능하지만, 이를 위해서는 수천 인년을 들여 충분히 똑똑한 컴파일러를 만들어야 하며—그나마도 언어 규칙 중 일부를 위반해야만 한다. 컴파일러 작성자는 C 프로그래머가 "하드웨어에 가까운" 코드를 작성한다고 믿게 해 주지만, C 프로그래머가 자신들이 빠른 언어를 사용하고 있다고 계속 믿게 하려면 기계어 코드는 매우 다른 동작을 생성해야 한다.
저수준 언어의 핵심 속성 중 하나는, 프로그래머가 언어의 추상 기계가 기반 물리 기계에 어떻게 매핑되는지를 쉽게 이해할 수 있다는 점이다. 이는 확실히 PDP-11에서는 사실이었다. 각 C 표현식이 한두 개의 명령으로 사소하게 매핑되었다. 마찬가지로, 컴파일러는 지역 변수를 스택 슬롯으로 간단히 내리고, 원시 타입을 PDP-11이 네이티브로 다룰 수 있는 것에 매핑했다.
그 이후로, C의 구현은 C가 손쉽게 하드웨어에 매핑되고 빠른 코드를 제공한다는 환상을 유지하기 위해 점점 더 복잡해졌다. 2015년 C 프로그래머, 컴파일러 작성자, 표준 위원회 구성원을 대상으로 한 설문조사는 C의 이해 가능성에 관한 여러 문제를 제기했다.3 예를 들어, C는 모든 필드가 대상에 유용한 정렬을 갖도록 구조체(배열은 제외)에 패딩을 삽입하는 것을 구현체에 허용한다. 구조체를 0으로 지운 다음 일부 필드를 설정하면, 패딩 비트가 모두 0일까? 설문 결과에 따르면, 36%는 그럴 거라고 확신했고, 29%는 모른다고 답했다. 컴파일러(와 최적화 수준)에 따라 그럴 수도 있고 아닐 수도 있다.
이는 꽤 사소한 예이지만, 상당수의 프로그래머가 틀리게 믿거나 확신하지 못한다. 포인터를 도입하면, C의 의미론은 훨씬 더 혼란스러워진다. BCPL 모델은 꽤 단순했다. 값은 워드다. 각 워드는 데이터이거나 어떤 데이터의 주소다. 메모리는 주소로 인덱싱되는 저장 셀의 평평한 배열이다.
반대로 C 모델은 분절(segmented) 아키텍처(포인터가 세그먼트 ID와 오프로 구성될 수 있음)나 심지어 가비지 수집되는 가상 머신 등 다양한 대상에서 구현할 수 있도록 의도되었다. C 명세는 이러한 시스템의 문제를 피하기 위해 포인터에 대한 유효한 연산을 신중히 제한한다. 결함 보고서 260의 응답1은 포인터 정의에 포인터 프로비넌스(pointer provenance) 개념을 포함했다:
"구현은 비트 패턴의 기원을 추적하고, 결정되지 않은 값을 나타내는 것들을 결정된 값을 나타내는 것들과 구별하여 취급할 수 있다. 또한 기원이 서로 다른 포인터를, 비트 단위로는 동일하더라도 서로 구별하여 취급할 수 있다."
불행히도 ‘provenance’라는 단어는 C11 명세에는 전혀 등장하지 않으므로, 그것이 의미하는 바는 컴파일러 작성자에게 달려 있다. 예를 들어 GCC와 Clang은 포인터를 정수로 변환했다가 다시 포인터로 변환할 때 그 프로비넌스가 캐스트를 통해 보존되는지 여부에서 서로 다르다. 컴파일러는 서로 다른 malloc 결과나 스택 할당에 대한 두 포인터가 비트 수준 비교에서는 같은 주소를 가리키는 것으로 보이더라도, 항상 같지 않다고 비교된다고 판단할 자유가 있다.
이러한 오해는 순전히 학문적 성격만의 문제가 아니다. 예를 들어, 부호 있는 정수 오버플로(C에서 정의되지 않은 동작)에서 비롯된 보안 취약점과, 널 검사 전에 포인터를 역참조하는 코드에서 비롯된 취약점이 관찰되었다. 후자의 경우 컴파일러에 포인터가 널일 수 없다는 신호를 주게 되는데, C에서는 널 포인터 역참조가 정의되지 않은 동작이므로 일어나지 않는다고 가정할 수 있기 때문이다(https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-1897).
이러한 문제들을 고려할 때, 프로그래머가 C 프로그램이 기반 아키텍처에 어떻게 정확히 매핑될지 이해할 수 있다고 주장하기는 어렵다.
Spectre와 Meltdown에 대한 제안된 수정은 상당한 성능 페널티를 부과하며, 지난 10년간의 마이크로아키텍처 발전을 상당 부분 상쇄한다. 아마도 이제 C 코드를 빠르게 만들려는 시도를 멈추고, 대신 빠르도록 설계된 프로세서에서 프로그래밍 모델이 어떤 모습일지를 생각해 볼 때일지 모른다.
우리는 전통적인 C 코드에 초점을 맞추지 않은 설계 예시를 여럿 가지고 있으며, 이는 영감을 제공한다. 예를 들어 Sun/Oracle의 UltraSPARC Tx 시리즈와 같은 고도로 멀티스레드화된 칩은 실행 유닛을 가득 채우기 위해 그만큼의 캐시를 필요로 하지 않는다. 연구 프로세서들2은 이 개념을 매우 큰 수의 하드웨어 스케줄 스레드로 확장했다. 이러한 설계의 핵심 아이디어는 고수준 병렬성이 충분하다면, 메모리에서 데이터를 기다리는 스레드를 중단시키고 다른 스레드의 명령으로 실행 유닛을 채울 수 있다는 점이다. 이러한 설계의 문제는 C 프로그램은 바쁜 스레드가 적다는 점이다.
ARM의 SVE(Scalar Vector Extensions)—그리고 버클리의 유사한 작업4—는 프로그램과 하드웨어 사이의 더 나은 인터페이스를 엿보게 해 준다. 기존 벡터 유닛은 고정 크기의 벡터 연산을 노출하고, 컴파일러가 알고리즘을 사용 가능한 유닛 크기에 맞추기를 기대한다. 반대로 SVE 인터페이스는 프로그래머가 이용 가능한 병렬성의 정도를 기술한다고 가정하고, 하드웨어가 이를 사용 가능한 실행 유닛 수에 맞춰 매핑한다. C에서 이를 사용하는 것은 복잡한데, 자동 벡터화기가 루프 구조에서 이용 가능한 병렬성을 추론해야 하기 때문이다. 반면 함수형 스타일의 map 연산으로 이를 생성하는 것은 사소하다. 매핑되는 배열의 길이가 이용 가능한 병렬성의 정도다.
캐시는 크지만, 크기만이 복잡성의 이유는 아니다. 캐시 일관성 프로토콜은 최신 CPU에서 빠르면서도 올바르게 만드는 것이 가장 어려운 부분 중 하나다. 관련 복잡성의 대부분은 데이터가 당연히 공유되면서도 가변적이라고 기대하는 언어를 지원하는 데서 온다. 대조적으로, 모든 객체가 스레드 로컬이거나 불변인(Erlang은 이를 더 단순화하여 스레드당 하나의 가변 객체만 허용한다) Erlang 스타일의 추상 기계를 생각해 보자. 이런 시스템의 캐시 일관성 프로토콜은 두 경우만 가지면 된다: 가변 또는 공유. 소프트웨어 스레드가 다른 프로세서로 마이그레이션될 때는 해당 캐시를 명시적으로 무효화해야 하지만, 이는 비교적 드문 연산이다.
불변 객체는 캐시를 더욱 단순화할 수 있을 뿐 아니라 여러 연산을 더 저렴하게 만든다. Sun Labs의 Project Maxwell은 캐시에 있는 객체와 영 제네레이션에 할당될 객체가 거의 같은 집합임을 지적했다. 객체가 캐시에서 퇴거(evict)되어야 하기 전에 죽는다면, 이를 주 메모리에 다시 쓰지 않음으로써 많은 전력을 절약할 수 있다. Project Maxwell은 캐시에서 실행되는 영 제네레이션 가비지 컬렉터(겸 할당자)를 제안했으며, 이를 통해 메모리를 빠르게 재활용할 수 있다. 힙의 객체가 불변이고 스택이 가변이라면, 가비지 컬렉터는 하드웨어로 구현하기 쉬운 매우 단순한 상태 기계가 되며, 비교적 작은 캐시를 더 효율적으로 사용할 수 있게 한다.
속도만을 위해, C 지원과의 절충 없이 설계된 프로세서는 아마도 많은 수의 스레드를 지원하고, 넓은 벡터 유닛을 가지며, 훨씬 단순한 메모리 모델을 가질 것이다. 이런 시스템에서 C 코드를 실행하는 것은 문제가 될 것이므로, 전 세계에 존재하는 방대한 레거시 C 코드를 고려하면 상업적 성공 가능성은 낮을 것이다.
소프트웨어 개발에서 병렬 프로그래밍은 어렵다는 흔한 미신이 있다. 이는 Alan Kay에게는 놀라운 일일 것이다. 그는 액터 모델 언어를 어린이들에게 가르칠 수 있었고, 그 아이들은 200개가 넘는 스레드를 가진 작동하는 프로그램을 작성했다. 이는 Erlang 프로그래머들에게도 놀랍다. 그들은 흔히 수천 개의 병렬 구성 요소를 가진 프로그램을 작성한다. 보다 정확히 말하면, C와 유사한 추상 기계를 가진 언어에서의 병렬 프로그래밍이 어렵다. 그리고 멀티코어 CPU에서부터 매니코어 GPU까지 병렬 하드웨어가 만연한 오늘, 이는 곧 C가 현대 하드웨어에 잘 매핑되지 않는다는 또 다른 방식의 표현일 뿐이다.
C 결함 보고서 260. 2004; http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm.
Chadwick, G. A. 2013. Communication centric, multi-core, fine-grained processor architecture. Technical Report 832. University of Cambridge, Computer Laboratory; http://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-832.pdf.
Memarian, K., Matthiesen, J., Lingard, J., Nienhuis, K., Chisnall, D. Watson, R. N. M., Sewell, P. 2016. Into the depths of C: elaborating the de facto standards. Proceedings of the 37 th ACM SIGPLAN Conference on Programming Language Design and Implementation: 1-15; http://dl.acm.org/authorize?N04455.
Ou, A., Nguyen, Q., Lee, Y., Asanović, K. 2014. A case for MVPs: mixed-precision vector processors. Second International Workshop on Parallelism in Mobile Platforms at the 41st International Symposium on Computer Architecture.
Perlis, A. 1982. Epigrams on programming. ACM SIGPLAN Notices 17(9).
David Chisnall
언어 간 인터페이스는 점점 더 중요해지고 있다.
https://queue.acm.org/detail.cfm?id=2543971
Mike Bland
무언가를 보면, 말하라.
https://queue.acm.org/detail.cfm?id=2620662
Friedrich Steimann, Thomas Kühne
모델이 소프트웨어 개발의 DNA가 될 수 있을까?
https://queue.acm.org/detail.cfm?id=1113336
David Chisnall은 케임브리지 대학교의 연구자로, 프로그래밍 언어 설계와 구현을 연구한다. 박사 학위를 마친 뒤 케임브리지에 오기 전까지 몇 년간 컨설팅을 했으며, 그동안 Xen과 Objective-C 및 Go 프로그래밍 언어에 관한 책과 수많은 글을 집필했다. 또한 LLVM, Clang, FreeBSD, GNUstep, Étoilé 오픈 소스 프로젝트에 기여하고 있으며, 아르헨티나 탱고를 춘다.
Copyright © 2018 권리자/저자 보유. 출판 권리는 ACM에 라이선스됨.

Queue 제16권 제2호에 처음 게재됨—
이 기사에 대한 의견은 ACM 디지털 라이브러리에서 남길 수 있다.
더 많은 관련 기사:
Matt Godbolt - C++ 컴파일러의 최적화
컴파일러에 더 많은 정보를 제공하면 트레이드오프가 있다: 컴파일 시간이 느려질 수 있다. 링크 타임 최적화와 같은 기술은 두 세계의 장점을 모두 제공할 수 있다. 컴파일러의 최적화는 계속 개선되고 있으며, 간접 호출과 가상 함수 디스패치의 다가오는 개선은 곧 더 빠른 다형성으로 이어질 수 있다.
Ulan Degenbaev, Michael Lippautz, Hannes Payer - 합작 투자로서의 가비지 컬렉션
컴포넌트 경계를 가로지르는 참조 사이클 문제를 해결하는 방법이 교차 컴포넌트 추적이다. 이 문제는 컴포넌트가 API 경계를 넘어 비평범한 소유권을 가진 임의의 객체 그래프를 형성할 수 있게 되는 즉시 나타난다. CCT의 증분 버전이 V8과 Blink에 구현되어, 안전한 방식으로 효과적이고 효율적인 메모리 회수를 가능하게 한다.
Tobias Lauinger, Abdelberi Chaabane, Christo Wilson - 너는 나에게 의존하지 말지어다
대부분의 웹사이트는 JavaScript 라이브러리를 사용하며, 그중 많은 것들이 취약한 것으로 알려져 있다. 문제의 범위와 라이브러리가 포함되는 많은 예기치 않은 방법을 이해하는 것이 상황을 개선하기 위한 첫걸음이다. 이 글에 포함된 정보가 커뮤니티를 위한 더 나은 도구, 개발 실무, 교육 노력을 고안하는 데 도움이 되길 바란다.
Robert C. Seacord - 미초기화 읽기
대부분의 개발자는 C에서 초기화되지 않은 변수를 읽는 것이 결함임을 이해하지만, 그래도 그렇게 하는 경우가 있다. C 표준(C11)의 현재 버전에서는 초기화되지 않은 객체를 읽을 때 무엇이 일어나는지 합의가 없다.3 표준의 예정된 C2X 개정에서 이러한 문제를 해결하기 위한 다양한 제안이 나와 있다. 따라서 지금은 기존 동작과 제안된 개정 사항을 이해하여 C 언어의 진화를 이끄는 데 영향을 미치기에 좋은 때다. C11에서 미초기화 읽기의 동작이 미정인 만큼, 신중함은 당신의 코드에서 미초기화 읽기를 제거할 것을 요구한다.
© ACM, Inc. All Rights Reserved.