[C++] printf, scanf, "\n"와 cin, cout, endl의 속도 차이의 이유

알고리즘 문제를 풀다보면, 입출력이 빈번한 경우 cin, cout을 사용하기 전에 입출력 속도를 빠르게 하기 위해서 ios::sync_with_stdio(false);와 cin.tie(0);을 작성하여 쓰는 것을 볼 수 있다. 실제로 printf, scanf와 거의 차이가 없을 정도로 속도가 향상된 것을 볼 수 있다. 이유는 이 함수 내부에서 입출력을 포함하여 버퍼를 'flush' 해주기 때문에 그렇다. 마찬가지의 이유로 줄바꿈을 나타낼 때 endl 대신에 "\n"을 사용하여 속도를 향상 시키자.

undefined behavior

코딩하다보면, undefined behavior를 생각할 때가 있다. 틈날 때마다 보기위해 여기 링크를 걸어둔다. https://stackoverflow.com/questions/367633/what-are-all-the-common-undefined-behaviours-that-a-c-programmer-should-know-a

C++에서 for문이 제대로 돌지 않는 현상?

이미지
동적배열의 크기만큼 for문을 돌리는 문제에서 디버깅하다 발견하게 되었다. 현상은 다음과 같다. 위는 프로그래머스 사이트에서 돌린 결과이고, 아래는 필자의 비쥬얼 스튜디오에서 돌린 결과이다. 의미는 같은데, 아래의 for문이 전혀 출력되지 않는 것을 볼 수 있다! 혹시나 싶어서, Java에서 이클립스 환경에서 돌려보았는데, 자바는 성공적으로 출력이 되었다. 어떤 조건일 때 출력이 정상적으로 되지 않을까 궁금했는데, i가 -이면 위와 같은 현상이 발생하였다. 글을 쓰다가, 원인을 찾던 중 size()의 자료형이 size_t 인 것을 발견하였다. 이는 C/C++에서 쓰이는 데이터 타입으로 자세한 내용은 위키에 있다.  https://namu.wiki/w/size_t 이 자료형은 언뜻 보면 int형과 같아보이는데, 사실은 unsigned 정수형이라는 것이다. (size_t는 '이론상 가장 큰 사이즈를 담을 수 있는 unsigned 데이터 타입'으로 정의) 따라서 int형으로 선언된 i와 unsigned 정수형인 size()와의 비교에서 오버플로우 현상이 나타나는 것이었다.

비트 연산에 대한 간단한 활용

비트 연산을 사용하면 빠르거나 간결한 코드를 작성할 수 있다. 이 외에도 활용 방안은 무궁무진하다. 앞으로 생각나는대로 추가할 생각이다. * 2의 거듭제곱과 관련된 수를 곱하거나 나눌 경우에  Shift(>>, <<) 연산을 사용할 수 있다. Example a /= 2;      <->     a >>= 1; a *= 16;     <->     a <<= 4; * 나머지 연산을 And(&) 연산으로 대체할 수 있는 경우가 있다. 2^n - 1에 해당 하는 숫자보다 작으면, 이를 나머지 연산으로 사용할 수 있다. 왜냐하면 이진수로 나타내었을 때, 모든 자리의 숫자가 1이기 때문이다. Example b = 3, m = 4; if (b % 15)     <->     if (b & 15)     <->     if (b & ((1<<m)-1)) * 어떤 숫자가 2의 거듭제곱인지 알고 싶을 때 And(&) 연산으로 간단하게 확인 가능하다. Example if (n & (n-1) == 0)     return true; * Swap 기능을 XOR(^) 연산으로 대체할 수 있는 경우가 있다. 정수형으로 나타낼 수 있는 자료형일 때만 가능하다. Example tmp = a;          <->        a ^= b; a = b;                ...

0으로 나누는 것에 대해서

Visual Studio처럼 0으로 나누었을 때, 0으로 처리해주는 개발환경이 있고 아닌 경우가 있다. 따라서, 0으로 나누어질 수 있는 경우가 있으면 예외처리를 하자.

참조와 값 복사의 실행속도 차이

알고리즘 문제를 풀다가 다른 분의 코드와 시간 복잡도 상에서는 거의 차이가 없는 것 같은데, 왜 이렇게 차이날까 하던 적이 있었다.  뭘까 뭘까 하면서 열심히 찾다가 값 복사에 의한 속도 차이였음을 알았다. 실제로 STL중에 vector 변수를 쓰다가 변수를 읽거나 변경할 때 그냥 넘겨주었던 경우가 있었다.  따라서 기본형의 변수가 아니면 C++, C# 기준으로, &(레퍼런스, 참조자)를 적극 활용하자. (C, Java는 참조자가 없지만 객체의 주소를 넘기는 방식인 pass-by-address로 전달 가능하며, Java는 따로 키워드를 쓸 필요가 없고, C는 *를 써서 가능하다. ) https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value * 혼동을 위한 정리 C++, C# : pass-by-value, pass-by-reference C, Java : pass-by-value, pass-by-address pass-by-value : 똑같이 복사하는 것 (별개의 데이터가 하나 더 생긴다.) pass-by-address : 값이 저장된 주소를 복사하는 것 (값만 바꿀 수 있고, 원래의 변수 상태를 바꿀 수 없다.) pass-by-reference : 변수 그 자체를 참조하는 것 (값과 원래의 변수 그 자체를 바꿀 수 있다.)

자료형과 오버플로우

간단하지만, 실수하기 쉬운 것들을 정리해보았다. (1) 현재 자료형에 대한 연산을 할 때, 오버플로우 가능성을 생각한다. 예:     long long solution(int w, int h)     {         return w * h; (X)         return (long long)w * h; (O)     }     int main()     {         int a = 100,000,000, b = 99,999,999;          solution(a, b);     } (2) 현재 자료형을 다른 자료형으로 형변환할 때, 오버플로우 가능성을 생각한다. 예:     long long a = 123,456,789,123,456,789;     double b = a;  (X) <- 대략적으로 10^16을 넘어가는 숫자를 그대로 담을 수 없다. (3) 영문과 숫자에 관련된 문자를 처리할 때, char가 127을 넘어가는 것을 생각한다. (C, C++) 대소문자를 변환할 때나 더해주는 작업을 할 때, 1byte 내에서 해결하려고(?) 문자형을 정수형으로 바꿔주지 않고 하는 경우가 많다. 이때, 실수하지 않도록 유의하자. (C#, Java의 경우는 char가 유니코드를 기반으로 한 2byte로 설정 되어있다.)   (4) 연산이나 함수 사용중에 형변환 되는 부분을 생각한다.