C++

C++ ios_base::sync (feat. [백준] 15552번)

zhelddustmq 2024. 9. 6. 12:27

www.acmicpc.net/problem/15552

 

 

cin, cout 썼다가 런타임 오류가 나셨나요?

참고하세요~


  • 알고리즘 [접근 방법]

방법 1 : C 표준 입출력 stdio.h

 

C언어의 표준 입출력인 scanf()와 printf() 쓰기

빠름의 정점인 C언어라 런타임 오류 안남

 

정답 코드

include <stdio.h>
 
int main(int argc, char const *argv[]) {
 
	int T, a, b;
 
	scanf("%d", &T);
	for(int i = 0; i < T; i++){
		scanf("%d %d", &a, &b);
		printf("%d\n", a + b);
	}
 
	return 0;
}

 

 

C++인데 C언어 문법 쓰기 싫다면?

 

방법 2 : iostream의 default 설정 수정

 

cin, cout만 쓸 경우에는 시간초과가 남. 즉, 위 방법에 비해 상대적으로 느린 입출력이란 것.

왜?

 

1) C++와 C 표준 스트림의 동기화 해제

 

ios_base::sync_with_stdio(false);

 

기본적으로 C++에서는 C++와 C의 표준 스트림이 동기화가 됨. 무슨 말인가 하면, C++에서 C와 C++ 각각의 스타일로 입출력을 받아도 서로 동기화하여 우리가 입력 혹은 출력하고자 하는 순서대로 결과를 얻을 수 있음. 즉, C와 C++가 동일한 버퍼를 공유한다는 것

 

이러한 동기화는 성능을 저하시키는 원인이 되지만, 두 스트림의 동기화는 우리가 입출력에 있어 C와 C++의 IO(Input-Output)을 혼용하여 쓸 때 매우 합리적이고 스레드로부터 안전하기 때문에 원래는 동기화 상태로 두는 것이 올바름

(std::cin은 stdin과 동기화 되며, std::cout은 stdout과 동기화 됨)

 

다만, 알고리즘 문제 풀이에서는 예외 처리나 멀티스레드 작업을 필요로 하지 않기 때문에 두 동기화를 끊어주어도 무방함

 

이 동기화를 끊는 다는 것은 C++ 표준 스트림이 독립적으로 IO 버퍼링을 할 수 있다는 것. 그렇게 되면 상당히 많은 양의 입출력이 있을 경우 동기화 되어있는 상태에 비해 성능이 많이 좋아짐

 

즉, ios_base에 있는 sync_with_stdio() 을 활용하여 위 코드처럼 적용시키면 동기화가 해제 됨. 함수를 해석해보면, stdio와의 싱크(동기화) 메소드인 것을 알 수 있음. 여기에 파라미터로 false을 해주면 동기화가 해제되게 된다.

 

그리고 중요한 점은 동기화를 해제했기 때문에 C와 C++ 스타일 중 하나를 선택해서 써야 함. 혼용X

 

 

 

 

2) 입력과 출력 연결을 끊어주기

 

cin.tie(NULL);	// 또는 cin.tie(nullptr), cin.tie(0) 으로 대체 가능

 

기본적으로 입력과 출력은 연결되어 있음

 

무슨 말인가 하면, 기본적으로 입력 요청이 들어오면 그 전에 출력 작업이 있었을 경우(출력 버퍼에 내용이 있는 경우) 버퍼를 비워(flush) 출력

좀 더 쉽게 말하자면 입력 요청을 통해 읽어들이게 될 경우, 전에 있던 출력 작업들을 콘솔창에 보이도록 버퍼를 비운다는 것

 

예를 들어

 

[입출력이 묶여있는 경우]

#include <iostream>
 
using namespace std;
 
int main(int argc, char const *argv[]) {
 
	ios_base::sync_with_stdio(false);
 
	int a;
	for (int i = 0; i < 10; i++) {
		cout << i << "번 째 입력\n";
		cin >> a;
	}
	return 0;
}

 

위와같이 하면 콘솔 창에서는 다음과 같이 결과를 얻을 수 있을 것이다.

 

0번 째 입력
2
1번 째 입력
4
2번 째 입력
25
3번 째 입력
54
4번 째 입력
43
5번 째 입력
23
6번 째 입력
43
7번 째 입력
6336
8번 째 입력
4352
9번 째 입력
24

 

 

 

하지만, 입력과 출력의 묶음을 풀어주면?

 

 

[입출력이 분리되어있는 경우]

#include <iostream>
 
using namespace std;
 
int main(int argc, char const *argv[]) {
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
 
	int a;
	for (int i = 0; i < 10; i++) {
		cout << i << "번 째 입력\n";
		cin >> a;
	}
	return 0;
}

 

아래와 같이 출력됨

32
24
14
52
25
24
23
1
34
342
0번째 입력
1번째 입력
2번째 입력
3번째 입력
4번째 입력
5번째 입력
6번째 입력
7번째 입력
8번째 입력
9번째 입력

-- 엥? 아닌데요...?

 

아마 대부분의 경우 두 번째 코드를 실행해보아도 위 결과처럼 안나오고 첫 번째 결과처럼 나옴

이유는 OS마다의 버퍼링 차이가 있는데, 윈도우의 경우에는 기본적으로 출력 하자마자 버퍼링 없이 콘솔에 출력이 됨. 하지만, 리눅스 계열의 경우 줄(개행)을 입력 받을 때 까지 버퍼링

 

그래서 실제로 리눅스에서 실행시키면 아래처럼 나옴.

 

 

 

중요한 건, 백준 채점 서버는 우분투(리눅스 계열)라는 것. (참고링크 : www.acmicpc.net/help/judge)

 

 

 

그리고 백준에서는 입력과 출력을 별도로 분리하고 있어 "출력문"만 채점 파일과 동일하면 되기 때문에 굳이 매번 출력 할 필요가 없음.

 

즉, 출력문만 동일하면 되기 때문에 굳이 입력 후에 해당 값을 매 번 출력 해줄 필요가 없다. 즉, 매번 출력을 flushing 시키지 않고 나중에 한 번에 비우도록 하는 것!

 

 

 

 

3) endl 대신 "\n" 쓰기

 

endl은 단순히 개행(줄바꿈)만 해주는 것이 아니라 출력 버퍼를 비우는 역할. 즉, 매 줄 바꿈마다 endl 을 쓰면 우리가 2번에서 다루었던 tie을 끊어주는 것의 효과를 볼 수가 없음.

 

이 출력 버퍼를 비우는 작업도 상당히 시간을 잡아먹는 작업이기 때문에 마지막 한 번에 출력을 비우는 것이 좋음(물론 버퍼가 꽉 차면 알아서 비워줌.)

 

 

 

 

ios_base::sync_with_stdio(false);	// C, C++ 동기화 해제
cin.tie(NULL);	// 입력과 출력을 분리
 
for(int i = 0; i < T; i++) {
	int a, b;
	cin >> a >> b;
	cout << a + b << "\n";	// endl 대신 \n 을 쓰기
}

 

 

그 외에 입출력을 직접 구현하는 방법도 있지만, 이 포스터에선 다루지 않음

 

정답 코드

#include <iostream>
 
using namespace std;
 
int main(int argc, char const *argv[]) {
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	int T, a, b;
 
	cin >> T;
	for(int i = 0; i < T; i++) {
		cin >> a >> b;
		cout << a + b << "\n";
	}
 
	return 0;
}