paint-brush
당신이 아마도 모르는 C와 C++의 숨겨진 보석~에 의해@udalov
11,160 판독값
11,160 판독값

당신이 아마도 모르는 C와 C++의 숨겨진 보석

~에 의해 Ilia6m2024/08/09
Read on Terminal Reader

너무 오래; 읽다

C++는 너무 많은 기능을 갖춘 훌륭한 언어입니다. 저는 C++ 개발자로서의 경력 동안 여러 번 "WTF" 순간을 경험했습니다. 이 스토리에서 C와 C++ 프로그래밍의 가장 재미있는 측면을 테스트 형태로 요약한 것을 발견하게 될 것입니다. 두 개 이상의 질문에 맞출 수 있는 방법은 없습니다.
featured image - 당신이 아마도 모르는 C와 C++의 숨겨진 보석
Ilia HackerNoon profile picture
0-item
1-item

C와 C++ 프로그래밍의 장엄한 세계에 합류할 준비가 되셨나요? 간단한 C++ 몇 줄을 읽고 자신의 존재에 대해 의문을 품고 싶으신가요?


만약 당신의 답이 "Yeh!", "Yep" 또는 "Why not?"라면, 당신의 지식을 테스트해 보세요. C 또는 C++와 관련된 여러 질문이 주어질 것입니다.


정답과 설명은 스토리의 끝에서 찾아보세요. 행운을 빌어요!

1. 가장 작은 프로그램

 main;


이 프로그램을 C 컴파일러를 사용하여 컴파일하려고 하면 무슨 일이 일어날까요?

  1. 컴파일되지 않습니다
  2. 컴파일은 되고 링크는 안됨
  3. 컴파일하고 링크할 것입니다

2. 포크

 #include <iostream> #include <unistd.h> int main() { for(auto i = 0; i < 1000; i++) std::cout << "Hello world!\n"; fork(); }


이 프로그램은 몇 줄을 인쇄할까요?

  1. 1000
  2. 1000 미만
  3. 1000 이상

3. 필요한 것은 인덱스뿐입니다

 #include <iostream> int main() { int array[] = { 1, 2, 3 }; std::cout << (4, (1, 2)[array]) << std::endl; }


이 프로그램은 무엇을 인쇄할까요?

  1. 1
  2. 2
  3. 3
  4. 4
  5. 컴파일되지 않습니다
  6. 한정되지 않은

4. 정규 표현식

 #include <regex> #include <iostream> int main() { std::regex re("(.*|.*)*O"); std::string str("0123456789"); std::cout << std::regex_match(str, re); return 0; }


이 정규 표현식이 입력 문자열과 일치하려면 얼마나 걸릴까요?


  1. 1밀리초
  2. 1초
  3. 1분
  4. 1시간
  5. 1년
  6. 영원히

5. 이동 및 람다

 #include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(Foo&&) { std::cout << "Foo(Foo&&)\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } }; int main() { Foo f; auto a = [f = std::move(f)]() { return std::move(f); }; Foo f2(a()); return 0; }


이 프로그램이 인쇄하는 마지막 줄은…


  1. Foo()
  2. Foo(Foo&&)
  3. Foo(const Foo&)

6. X와 막대

 #include <iostream> int x = 0; int bar(int(x)); int main() { std::cout << bar; }


이 프로그램은 무엇을 인쇄할까요?

  1. 0
  2. 1
  3. 0x0
  4. 컴파일되지 않습니다
  5. 링크하지 않습니다

7. 건설자

 #include <iostream> struct Foo { Foo() { std::cout << "Foo()\n"; } Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; } Foo(int) { std::cout << "Foo(int)\n"; } Foo(int, int) { std::cout << "Foo(int, int)\n"; } Foo(const Foo&, int) { std::cout << "Foo(const Foo&, int)\n"; } Foo(int, const Foo&) { std::cout << "Foo(int, const Foo&)\n"; } }; void f(Foo) {} struct Bar { int i, j; Bar() { f(Foo(i, j)); f(Foo(i)); Foo(i, j); Foo(i); Foo(i, j); } }; int main() { Bar(); }


이 프로그램이 인쇄하는 마지막 줄은…

  1. Foo(int, int)
  2. Foo(const Foo&, int)
  3. Foo(int, const Foo&)
  4. Foo(int)

결론 대신

이런 특이한 조각들을 야외에서 발견하지 못하기를 바랍니다.

답변

  1. 가장 작은 프로그램


    이것은 합법적인 C 코드입니다. 성공적으로 컴파일하고 링크할 것입니다. 실행하려고 하면 충돌합니다. main; - 은 전역 변수입니다.


    C 코드에서는 많은 것을 생략할 수 있습니다. 예를 들어 전역 변수의 유형을 생략할 수 있습니다. 기본적으로 컴파일러는 이 유형을 int 로 가정합니다. 또한 C에는 이름 망글링이 없으므로(C++와 달리) 링크할 때 변수 main 과 함수 main 을 구별할 방법이 없습니다.


    따라서 컴파일러는 유효한 코드를 컴파일하고, 링커는 개체 파일에서 main 이라는 이름의 파일을 찾아 프로그램을 연결합니다.


  2. 포크


    이것은 C 또는 C++ 기능보다는 POSIX 기능에 가깝습니다. IO 작업 구현은 성능 최적화를 위해 버퍼를 사용합니다. fork 호출하면 OS는 프로세스 메모리의 복사-쓰기 복제본을 생성하고 IO 버퍼도 복제될 가능성이 높으며 버퍼링된 문자열은 1000회 이상 인쇄될 가능성이 높습니다.


  3. 필요한 것은 인덱스뿐입니다


    답은 3입니다


    이 코드를 이해하기 위해 C 및 C++에서 인덱스가 작동하는 방식을 자세히 살펴보겠습니다. array[index]*(array + index) 와 같고, (index + array) 와 같고, index[array 와 같습니다.

    두 번째 단서는 연산자 , 입니다. 이진 연산자로, 왼쪽 인수를 버리고 오른쪽 인수를 반환합니다.


  4. 정규 표현식


    무슨 일이 일어날지 예측하는 것은 불가능 합니다! 행동은 구현에 달려 있습니다.


    제 환경에서 이 프로그램은 The complexity of an attempted match against a regular expression exceeded a pre-set level. 예외를 발생시킵니다.


    다른 가능한 옵션은 놀랍게도 오랜 시간이 걸리거나 예상대로 작동합니다. 정규 표현식을 구현하는 데 두 가지 가능한 접근 방식이 있기 때문입니다.


    첫째 - 정규 표현식을 유한 오토마타 O(n**2) (n - 패턴 길이)로 변환하고, 문자열 일치 O(m) (m - 문자열 길이). 이 접근 방식은 백트래킹을 지원하지 않습니다.


    두 번째 - 탐욕적 접근법 + DFS, 백트래킹을 지원하지만 특정 패턴에서는 지수적 시간 복잡도가 발생하기 쉽습니다.


  5. 이동 및 람다


    답은 Foo(const Foo&) 입니다. 람다는 기본적으로 불변이며, [] 로 람다에 캡처된 모든 값은 암묵적으로 const 입니다. 이를 통해 람다에 대한 멱등 동작이 잠금 해제됩니다.


    f 이동하면 const Foo&& 가 생성됩니다. const Foo&& 는 이상한 유형이므로 컴파일러는 Foo 그냥 복사합니다.


    이 문제를 해결하는 방법은 두 가지가 있습니다.


    1. 변경 가능한 람다 생성

       auto a = [f = std::move(f)]() mutable { return std::move(f); };


    2. 생성자 Foo(const Foo&&) 선언합니다.


  6. X와 막대

    프로그램은 1 인쇄합니다 .


    int bar(int(x)); — 함수를 선언하는 이상한 방법으로, int bar(int x); 와 같습니다.

    형 변환과 혼동하셨다면, int bar((int(x))); 가 바로 형 변환입니다.


    그런 다음 함수 주소를 bool 로 암묵적으로 캐스팅하려고 하면 이러한 캐스팅의 결과는 항상 true 입니다.


    bar() 함수는 한 번도 사용되지 않았는데, 이를 통해 링크할 때 참조되지 않은 심볼 오류를 피할 수 있습니다.


  7. 생성자

    마지막 줄은 Foo(const Foo&, int) 입니다.


    Foo(i) 는 변수 선언이며 Foo i 와 동일합니다. 따라서 i 라는 이름의 클래스 멤버는 이 범위에서 숨겨집니다.