검색결과 리스트
언어에 해당되는 글 15건
- 2019.09.03 [C++] R-Value Reference와 Move Semantic
- 2019.09.03 [C++] Q: 왜 동적배열이 vector가 된건가요?
- 2018.12.30 [C++] C++17 optional
- 2018.12.30 [C++] C++17 variant
- 2018.12.30 [C] 문자열 유사클래스 구현
- 2018.12.30 [C] Q: 스택의 사이즈는 왜이렇게 작나요?
- 2018.12.30 [C] ()와 (void)의 차이
- 2018.12.30 [C] Q: scanf에서 오류가 나요. in Visual Studio
글
[C++] R-Value Reference와 Move Semantic
이게 대체 뭐하는 놈인고 하면, 아주 원론적인 부분까지 접해들어갈 필요가 있다.
아래 코드를 봐보자.
int num;
num = 33;
L: 여기서 num은 왼쪽에 있다. 그리고 이름이 있다. 그리고 표현식이 종료되어도 존재한다. 또한 메모리 셀이므로 주소연산(&)을 사용할 수 있다. 이것이 왼값이다.
R: 오른쪽에는 33이 있다. 이건 이름이 없다. 그냥 값이 num으로 전달될 뿐이고, 표현식이 끝나면 증발한다. 또한 상수이므로 주소연산을 사용할 수 없다. 이것이 오른값이다.
const string& s... 같은,
이제까지 우리가 사용했던 참조는 왼값참조였다.
기존에 존재하던 변수(왼값)를 참조했으니까.
하지만 이제 우리가 접근해볼 오른값 참조는 저런 이름없는 임시 값들마저 참조할 수 있다.
표기법은 아래와 같이, &대신에 &&를 사용한다.
int && ref = 333; //오른값 333을 참조
왼값도 std::move를 사용하면 오른값 참조에 묶을수 있다.
std::string s = "text";
std::string&& ref = std::move(s);
//이름이 move지만 진짜 이동하는건 아니고 &&참조로 캐스팅만 해줌
오른값 참조자도 기존의 왼값 참조자처럼 값에 접근해서 이래저래 쓸수 있다.
근데 이게 다라면 이걸 구분해서 쓸 이유가 전혀 없다. 그냥 왼값참조 써도 똑같이 되잖아?
그렇다. 이 오른값 참조로 값이 다른 변수에 전달되고, 그 변수의 타입에 해당하는 이동 생성자(혹은 대입자)가 있다면, 무브 시맨틱(Move Semantic)이라는 것이 발생한다.
말 그대로 이동(move)을 수행한다.
컴퓨터에서 무거운 영상 파일들을 옮겨본 적이 있을것이다.
파일이 너무 무겁다면 그 파일의 복사하고 붙여넣는(Ctrl+c,v) 속도도 엄청하게 느려진다.
하지만 파일을 잘라내고 붙일(Ctrl+x,v) 때는 별다른 시간이 소요되지 않고 뚝딱 완료된다. 이것이 파일이 한 군데만 존재할 것을 보장하는 데서 발생하는 일종의 이동이다.
앞서 언급한 무브시맨틱도 이와 같은 원리라 보면 된다.
예시를 한번 보자.
//가정1: s 안에 담긴 문자열은 크기가 크다.
std::string s = "...";
//이 시점에서 s는 더이상 필요가 없다.
//s2에서 옮겨서 s2로만 쓰면 된다.
std::string s2 = s;
이러면 s2의 복사생성자가 호출되면서 s의 내부값을 일일이 복사해서 가져간다.
기존 s의 크기가 클수록 소요되는 시간도 선형적으로 증가할 것이다.
하지만 이렇게 한다면
std::string s2 = std::move(s);
//s를 std::string&&으로 캐스팅
//s2의 이동생성자 호출
s의 길이와 상관없이 이 작업은 아주 작은 상수시간에 종료된다.
그리고 기존의 s는 텅 비게된다.
그리고 무브시맨틱은 연산중에 발생한 임시객체를 다룰때도 효용을 보인다.
std::string s = "hello";
std::string s2 = " world";
std::string s3 = s+s2;
옛날 방식대로 생각해보자. 위 코드의 세번째줄에서 s와 s2가 더해져서 둘이 이어진 임시객체 std::string("hello world")가 생성된다. 여기까진 좋다. 그럼
std::string s3 = std::string("hello world");
의 형태가 된다.
그럼 저 임시객체의 값을 복사해가기 위해 복사생성자가 호출되고 저 값들을 전부 복사해갈 것이다.
당장 저 길이라면 상관없지만, 임시객체가 저것보다 훨씬 크다면 이건 문제가 된다.
하지만 이제 우리에겐 무브 시맨틱이 있다.
s+s2의 결과물은 오른값이다.
그러므로 이동생성자가 호출돼서 s+s2의 결과값이 낭비없이 s3에게 쏙 넘어간다.
그리고 이런 성능의 향상 빼고도, 이 무브시맨틱은 하나의 장점이 또 있다.
무브시맨틱은 말 그대로 이동이다.
값이 아래에 있던 것이 위로 가면, 그 값은 위에만 존재한다. 아래에는 존재하지 않는다.
그러므로 이동의 동작은 의미론적으로 유일성(unique)을 보장한다. 복사와 다르게.
그래서 스마트포인터 std::unique_ptr 같은 경우는 이런 이동만을 행할 수 있다. 복사는 금지되어있다.
이 외에도 복사는 금지하고 이동만을 허가하는 클래스들이 꽤 있다.
auto p = std::make_unique<T>(...);
std::unique_ptr<T> p2 = p; //에러! 복사 불가
std::unique_ptr<T> p3 = std::move(p); //p->p3 이동. p는 null이 됨
아 그리고 오른값참조가 등장하면서 원래 4개였던 클래스의 기본 함수들이, 6개로 늘어났다.
클래스명이 T일 경우, 기본 함수들은 아래와 같다.
T()=default; //기본 생성자
~T() = default; //소멸자
T(const T&) = default; //복사생성자
T(T&&) = default; //이동생성자
T& operator=(const T&) = default; //복사대입자
T& operator=(T&&) = default; //이동대입자
이동생성자와 이동대입자는 구현할 필요가 없다. 복사생성자와 같은 얕은복사의 문제는 없기 때문에 그냥 =default만 해줘도 된다.(컴파일러에게 자동구현시키기)
그래도 만약 구현하고 싶다면, 아래처럼 하면 된다.
//가정: 타입 T에는 멤버변수로 a와 b를 가진다.
//가정: a와 b는 전부 이동이 가능하다.
T(T&& other) :
a(std::move(other.a)),
b(std::move(other.b)
{}
T& operator=(T&& other)
{
this->a=std::move(other.a);
this->b=std::move(other.b);
return *this;
}
아무튼 오른값 참조는 굉장히 유용하다.
문제는 자잘한 규칙과 함정이 많고, 복잡하다는 것인데
가장 헷갈릴만한 것은, 오른값 참조자는 오른값이 아니라는 것이다. 오른값 참조자 자체는 이름도 있고, 주소연산도 가할 수 있으며, 표현식이 종료되어도 생존한다.
때문에 아래와 같은 경우는 이동이 발생하지 않는다.
std::string s = "...";
std::string&& s2 = std::move(s); //s->s2 이동
std::string s3 = s2; //s2->s3 이동 실패. 그냥 복사 생성자 호출
이동의 과정은 순수하게 오른값으로만 전달되어야 한다.
아래와 같이 고치면 이동이 수행된다.
std::string s = "...";
std::string&& s2 = std::move(s); //s->s2 이동
std::string s3 = std::move(s2); //s2->s3 이동
그리고 오른값 참조자는 const를 붙이면 안된다.
에러는 나지 않지만 이동이 실패한다.
더 자세한 사항들은 모던 이펙티브 C++를 참조하길 바란다.
'언어 > C++' 카테고리의 다른 글
[C++] Q: 왜 동적배열이 vector가 된건가요? (0) | 2019.09.03 |
---|---|
[C++] C++17 optional (0) | 2018.12.30 |
[C++] C++17 variant (0) | 2018.12.30 |
글
[C++] Q: 왜 동적배열이 vector가 된건가요?
Q: 수학적 의미의 벡터는 대략 알긴 하는데요.
c++의 벡터에 대한 뜻은 알지 못해서 이렇게 질문을 올립니다.
A: vector의 수학적 정의는 집합 Sn의 멤버면서 특정 집합 S에 들어있는 값의 정렬된 시퀀스입니다.
이게 c++의 벡터가 저장하는거죠
A2: 이건 STL의 설계자인 Alex Stepanov가 built-in 배열과 구분하기 위해 vector라고 부른 데서 유래합니다.
근데 수학적 의미의 vector는 길이가 고정된 시퀀스죠... c++의 vector는 길이가 변하는 시퀀스고요.
알렉스도 자신의 실수를 인정했습니다.
게다가 C++0X(C++11)에서는 수학적 의미의 vector와 같은 기능을 하는 클래스를 'array'로 정의해버렸습니다.
엉망진창이네요.
알렉스의 교훈: 이름은 언제나 신중하게 결정하자
'언어 > C++' 카테고리의 다른 글
[C++] R-Value Reference와 Move Semantic (0) | 2019.09.03 |
---|---|
[C++] C++17 optional (0) | 2018.12.30 |
[C++] C++17 variant (0) | 2018.12.30 |
글
[C++] C++17 optional
C++17부터 옵셔널 라이브러리가 추가되었다.
이 옵셔널 타입은 원래 함수형 언어에서 나온 개념인데, 개념적으로는 모나드라고 부른다.
예외처리 방법 중 하나며, 안전성이 높아서 명령형 언어들에서도 채용을 많이 하는 편이다.
C#의 Nullable이 그렇고, Swift의 옵셔널, 코틀린의 ??, 러스트의 Result 등이 있다.
단어 자체의 뜻을 보면 '선택적인'이라는 뜻인데. 값이 선택적으로 있을수도 없을수도 있다는 뜻이다.
좀 다르긴 하지만 포인터 타입도 이런 면에서 옵셔널의 특성을 가진다고 할 수도 있다.
널 포인터를 가지면 값이 없는 것이고 뭐라도 갖고있으면 주소가 있는거니까 값이 있는 것이다.
옵셔널은 이러한 특성을 포인터뿐만 아니라 모든 타입에 적용시킬 수가 있다.
게다가 값이 들어있는지 아닌지 명시적으로 확인을 하게 해서 포인터에 비하면 아주 안전하다.
그대신 귀찮은 면이 있긴 하지만 어느정도 감수할만한 부분이라 생각된다.
옵셔널로 선언된 객체는 그 자체로는 사용할 수 없다.
값이 들어있는 상태인지 아닌지 모르기 때문이다.
옵셔널의 값을 사용하려면 value 메서드를 쓰면 된다.
다만 값이 없을 때 쓰면 예외가 뜨니까 has_value나 operator bool로 값 여부를 체크하고 사용해야 한다.
"John"이라는 값이 멀쩡히 들어있으니 아주 잘 수행된다.
값이 없으니 "??"가 나온다.
포인터에서 빈 주소를 표현하는 용도로 nullptr가 예약된것처럼
빈 옵셔널을 표현하는 객체로 nullopt가 있다.
근데 값을 사용할때마다 조건문을 달아주는건 꽤 귀찮은 작업이다.
위에서 했던건 value_or 메서드 하나로 퉁칠 수 있다.
이건 값이 들어있으면 값 그냥 반환하고, 값이 없으면 인자로 받은걸 반환한다.
'언어 > C++' 카테고리의 다른 글
[C++] R-Value Reference와 Move Semantic (0) | 2019.09.03 |
---|---|
[C++] Q: 왜 동적배열이 vector가 된건가요? (0) | 2019.09.03 |
[C++] C++17 variant (0) | 2018.12.30 |
글
[C++] C++17 variant
variant는 진화된 공용체다.
기존의 union은 문제가 많았다.
예를 들어 기존의 union을 쓸 때,
double 버전으로 값을 넣고 int 버전으로 사용을 한다 해도 아무런 문제 없이 작동이 된다.
하지만 출력값을 보면 알겠지만 값이 정상적이지 않다.
C의 공용체는 아무런 타입 검사를 수행하지 않는다. 이 때문에 C의 공용체는 타입-safe하지 못하다고 한다.
아무튼 저런 맛이 간 출력값이 나온 이유는 아무런 조치 없이 메모리에 존재하는 비트값을 그대로 읽어왔기 때문이다. 정수 타입과 실수 타입의 메모리 구조는 완전히 다르다. 게다가 엔디언과 바이트 크기까지 고려하면 같은 계열의 타입이라도 괴리가 발생할 수가 있다.
물론 이런걸 이용해서 편법적으로 사용할 수도 있지만,
실제로 이런 식의 접근은, 99% 사용자의 실수에서 발생한다. 그리고 실수는 결함으로 이어진다.
union은 이처럼 안전성에 문제가 많다.
게다가 기존의 union은 생성자/소멸자/가상메서드가 없는 이른바 POD 타입만을 멤버로 둘 수가 있다는 심각한 기능적 결함이 있었다.
다시말해 C++스타일의 타입은 제대로 담아둘수 없었다는 것이다.
C++17에서는 이런 문제들을 해결한 variant라는 멋진 녀석을 내놓았다.
variant는 타입-safe한 공용체 템플릿이다.
당연히 C++ 스타일의 클래스도 멤버로 둘 수 있다.
기존의 공용체와 다른 특징으로, 이녀석은 반드시 초기화를 해야만 접근 타입을 바꿀 수가 있다.
해당 타입으로의 초기화를 수행하지 않고 접근을 한다면 예외가 발생한다.
아마 이런 방식을 통해서 타입체킹도 엄격하게 하고 생성자/소멸자도 써먹을 수 있게 한 것 같다.
일단 아래 코드는 아주 정상적으로 작동하는 코드다.
int 타입으로 값 대입 한번 해주고, 접근해서 출력하고.
double로 초기화해주고, 접근해서 출력한다.
하지만 초기화를 수행하지 않으면 이렇게 예외가 나온다.
이렇게
그리고 애초에 없는 타입이나 인덱스는 당연히 컴파일부터가 안 된다.
암튼 그렇다.
자세한 건 레퍼런스를 참조
'언어 > C++' 카테고리의 다른 글
[C++] R-Value Reference와 Move Semantic (0) | 2019.09.03 |
---|---|
[C++] Q: 왜 동적배열이 vector가 된건가요? (0) | 2019.09.03 |
[C++] C++17 optional (0) | 2018.12.30 |
글
[C] 문자열 유사클래스 구현
oop 흉내.
C의 한계로, 메서드마다 첫번째 인자로 객체 자신의 주소를 보내줘야 함.
https://github.com/myyrakle/c-oop-string/blob/master/main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> //기본 초기화 목록입니다. //기본 빈 값을 넣어주고, //유사 멤버함수들을 전부 넣어줍니다. #define NullString \ { \ NULL, 0, \ _string_set, \ _string_setn, \ _string_gets, \ _string_get_length, \ _string_copy_from, \ _string_clone, \ _string_move, \ _string_append, \ _string_append_raw, \ _string_swap, \ _string_clear, \ _string_is_empty, \ _string_compare, \ _string_compare_raw, \ _string_is_same, \ _string_is_same_raw, \ _string_format, \ _string_append_format, \ _string_at, \ _string_ptr_at, \ _string_findc, \ _string_finds, \ _string_finds_raw, \ } //C스타일 문자열 유사클래스입니다. struct String { char* data; size_t length; //문자열을 할당합니다. void (*set)(struct String*, const char*); void (*setn)(struct String*, const char*, size_t); //문자열을 가져옵니다. const char* (*gets)(const struct String*); //문자열의 길이를 가져옵니다. size_t (*get_length)(const struct String*); //깊은복사를 행합니다. void (*copy_from)(struct String*, const struct String*); //깊게 복제된 객체를 반환합니다. struct String (*clone)(const struct String*); //이동합니다. 자신은 clear가 됩니다. struct String (*move)(struct String*); //이어붙입니다. void (*append)(struct String*, const struct String*); //String 버전입니다. void (*append_raw)(struct String*, const char*); //생 문자열 버전입니다. //교환합니다. void (*swap)(struct String*, struct String*); //지워버립니다. void (*clear)(struct String*); //비어있는지 여부를 확인합니다. int (*is_empty)(const struct String*); //비교해서 같으면 0... 뭐 그런거요 int (*compare)(const struct String*, const struct String*); //String 버전입니다. int (*compare_raw)(const struct String*, const char*); //생 문자열 버전입니다. //같으면 1. 다르면 0. int (*is_same)(const struct String*, const struct String*); //String 버전입니다. int (*is_same_raw)(const struct String*, const char*); //생 문자열 버전입니다. //포맷팅합니다. void (*format)(struct String*, const char*, ...); //포맷팅해서 그대로 대입합니다. void (*append_format)(struct String*, const char*, ...); //포맷팅한 문자열을 이어붙입니다. //인덱싱합니다. 범위를 벗어나면 EOF를 줘요. int (*at)(const struct String*, size_t); //그냥 그 문자를 반환합니다. int* (*ptr_at)(struct String*, size_t); //그 문자의 주소를 반환합니다. //문자나 문자열을 탐색해서 인덱스를 줍니다. //못찾으면 EOF를 반환합니다. int (*findc)(const struct String*, char c); //문자를 찾습니다. int (*finds)(const struct String*, const struct String*); //String 문자열을 찾습니다. int (*finds_raw)(const struct String*, const char*); //생 문자열을 받아서 찾습니다. }; typedef struct String String; typedef String string; /*빌더 함수 선언*/ String make_string(const char*); String make_nullstring(); /*String 유사메서드*/ void _string_set(String*, const char*); void _string_setn(String*, const char*, size_t); const char* _string_gets(const String*); size_t _string_get_length(const String*); void _string_copy_from(String* self, const String* other); String _string_clone(const String* self); String _string_move(String* self); void _string_append(String* self, const String* other); void _string_append_raw(String* self, const char* other); void _string_swap(String* self, String* other); void _string_clear(String* self); int _string_is_empty(const String* self); int _string_compare(const String* self, const String* other); int _string_compare_raw(const String* self, const char* other); int _string_is_same(const String* self, const String* other); int _string_is_same_raw(const String* self, const char* other); void _string_format(String* self, const char* format,...); void _string_append_format(String* self, const char* format, ...); int _string_at(const String* self, size_t index); int _string_ptr_at(String* self, size_t index); int _string_findc(const String* self, char c); int _string_finds(const String* self, const String* key); int _string_finds_raw(const String* self, const char* key); /*유사메서드 정의*/ void _string_set(String* self, const char* s) { self->length = strlen(s); free(self->data); self->data = malloc(sizeof(char)*self->length +1); strncpy(self->data, s, self->length+1); } void _string_setn(String* self, const char* s, size_t len) { self->length = len; free(self->data); self->data = malloc(sizeof(char)*self->length +1); strncpy(self->data, s, self->length +1); } const char* _string_gets(const String* self) { return self->data; } size_t _string_get_length(const String* self) { return self->length; } void _string_copy_from(String* self, const String* other) { self->length = other->length; free(self->data); self->data = malloc(sizeof(char)*self->length +1); strncpy(self->data, other->data, self->length +1); } String _string_clone(const String* self) { String temp = make_nullstring(); temp.setn(&temp, self->data, self->length); return temp; } String _string_move(String* self) { String temp = make_nullstring(); temp.data = self->data; temp.length = self->length; self->data=NULL; self->length=0; return temp; } void _string_append(String* self, const String* other) { self->length+=other->length; realloc(self->data, self->length+1); strncat(self->data, other->data, self->length+1); } void _string_append_raw(String* self, const char* other) { self->length+=strlen(other); realloc(self->data, self->length); strncat(self->data, other, self->length+1); } void _string_swap(String* self, String* other) { char* t = self->data; size_t t2 = self->length; self->data = other->data; self->length = other->length; other->data = t; other->length = t2; } void _string_clear(String* self) { self->length=0; free(self->data); self->data=NULL; } int _string_is_empty(const String* self) { return self->data==NULL; } int _string_compare(const String* self, const String* other) { return strcmp(self->data, other->data); } int _string_compare_raw(const String* self, const char* other) { return strcmp(self->data, other); } int _string_is_same(const String* self, const String* other) { return strcmp(self->data, other->data)==0; } int _string_is_same_raw(const String* self, const char* other) { return strcmp(self->data, other)==0; } enum { STRING_BUFFER_SIZE = 200 //포맷팅 함수에서 사용할 버퍼 크기. 늘려도 됨 }; void _string_format(String* self, const char* format,...) { char buffer[STRING_BUFFER_SIZE]; va_list args; va_start(args, format); vsprintf(buffer, format, args); self->set(&self, buffer); va_end(args); } void _string_append_format(String* self, const char* format, ...) { char buffer[STRING_BUFFER_SIZE]; va_list args; va_start(args, format); vsprintf(buffer, format, args); self->append_raw(&self, buffer); va_end(args); } int _string_at(const String* self, size_t index) { if(index<0 || index>=self->length) return EOF; else return self->data[index]; } int _string_ptr_at(String* self, size_t index) { if(index<0 || index>=self->length) return EOF; else return &(self->data[index]); } int _string_findc(const String* self, char c) { for(int i=0; i<self->length; ++i) { if(self->data[i]==c) return i; } return EOF; } int _string_finds(const String* self, const String* key) { const char* finded = strstr(self->data, key->data); if(finded==NULL) return EOF; else return (int)finded - (int)(self->data); } int _string_finds_raw(const String* self, const char* key) { const char* finded = strstr(self->data, key); if(finded==NULL) return EOF; else return (int)finded - (int)(self->data); } //String 유사클래스 유사객체를 생성해서 반환합니다. String make_string(const char* s) { String temp = NullString; temp.set(&temp, s); return temp; } //빈 유사객체를 생성해서 반환합니다. String make_nullstring() { String temp = NullString; return temp; } const int main(void) { string s = make_string("hello"); printf(s.data); return 0; } | cs |
'언어 > C' 카테고리의 다른 글
[C] Q: 스택의 사이즈는 왜이렇게 작나요? (0) | 2018.12.30 |
---|---|
[C] ()와 (void)의 차이 (0) | 2018.12.30 |
[C] Q: scanf에서 오류가 나요. in Visual Studio (0) | 2018.12.30 |
[C] C99~C11 조금 낯선 기능들 (0) | 2018.12.30 |
[C] Q: 프로그램이 실행되자마자 꺼져요! (0) | 2018.12.30 |
글
[C] Q: 스택의 사이즈는 왜이렇게 작나요?
Q: When you allocate memory on the heap, the only limit is free RAM (or virtual memory).
힙에 메모리를 할당할 때는 램이나 가상메모리의 크기 내에서는 자유롭게 사이즈를 줄 수 있죠.
It makes Gb of memory.
그래서 보통 기가바이트 단위도 쓸 수가 있어요.
그런데 스택은 왜이렇게 작은거죠? 보통 1메가정도밖에 안되더라고요.
What technical reason prevents you to create really big objects on the stack ?
기술적인 이유 때문에 스택에 큰 객체를 두지 못하는건가요?
...
A: My intuition is the following.
내 생각엔 이래요.
The stack is not as easy to manage as the heap.
스택은 힙처럼 관리하기가 쉽지 않아요.
The stack need to be stored in continuous memory locations.
스택은 연속적인 메모리 위치에 저장될 필요가 있거든요.
This means that you cannot randomly allocate the stack as needed, but you need to at least reserve virtual addresses for that purpose.
스택에는 필요에 따라 랜덤으로 할당을 할수 없단거죠.
하지만 최소한 가상 주소는 예약을 해놔야 합니다.
The larger the size of the reserved virtual address space, the fewer threads you can create.
예약된 가상 주소 공간이 커질수록 만들수 있는 스레드 수는 적어져요.
예를 들어 32비트 앱은 보통 2기가의 가상주소 공간을 가집니다.
This means that if the stack size is 2MB (as default in pthreads), then you can create a maximum of 1024 threads.
그러니까 스택의 크기가 2메가면 최대 1024개의 스레드를 생성할 수가 있는거죠.
This can be small for applications such as web servers.
웹서버 같은 앱들은 이게 적어도 되는데요.
Increasing the stack size to, say, 100MB (i.e., you reserve 100MB, but do not necessarily allocated 100MB to the stack immediately), would limit the number of threads to about 20, which can be limiting even for simple GUI applications.
스택의 크기를 100으로 늘리면(100메가를 예약해도 바로 100메가를 할당하진 않음), 스레드 수를 20개 정도로 제한할 수 있죠. 이건 간단한 gui 앱에서도 제한이 될수 있어요.
흥미로운 문제는, 64비트 플랫폼에서도 여전히 이 제한이 존재한다는 거에요.
I do not know the answer, but I assume that people are already used to some "stack best practices":
이 문제에 대해서는 답을 드리진 못하겠지만, 아마 사람들이 스택의 그 "무난한 관행"에 익숙해졌기 때문인것 같네요.
be careful to allocate huge objects on the heap and, if needed, manually increase the stack size.
큰 객체는 힙에 놓고 필요할 때만 스택의 크기를 늘리는게 좋다는 겁니다.
Therefore, nobody found it useful to add "huge" stack support on 64-bit platforms.
그래서 64비트 플랫폼에서 어떤 환경도 기본으로 "큰" 스택을 지원하지 않는거에요.
'언어 > C' 카테고리의 다른 글
[C] 문자열 유사클래스 구현 (0) | 2018.12.30 |
---|---|
[C] ()와 (void)의 차이 (0) | 2018.12.30 |
[C] Q: scanf에서 오류가 나요. in Visual Studio (0) | 2018.12.30 |
[C] C99~C11 조금 낯선 기능들 (0) | 2018.12.30 |
[C] Q: 프로그램이 실행되자마자 꺼져요! (0) | 2018.12.30 |
글
[C] ()와 (void)의 차이
그래서 위에서 아래에 있는 함수를 갖다 쓰려면 애가 찾질 못하죠.
그래서 전방선언-프로토타입이란걸 위에다 붙여줍니다. 보통 헤더에 분리해서 넣어주죠.
이렇게요.
자 근데. 선언부의 파라미터를 전부 지우면 어떨까요?
시그너처가 다르니 에러가 날까요?
한번 봅시다.
잘 됩니다.
선언부에서의 빈 ()는 이건 뭐든 들어갈수 있어! 라는 뜻이기 때문이죠."이건 파라미터를 안 받아!"라는 의미를 부여하려면 void를 명시해줍니다.
이제 (void)라고 확실히 명시가 됐으니, 아래에 있는 정의부와 일치하지 않아서 에러가 납니다.
'언어 > C' 카테고리의 다른 글
[C] 문자열 유사클래스 구현 (0) | 2018.12.30 |
---|---|
[C] Q: 스택의 사이즈는 왜이렇게 작나요? (0) | 2018.12.30 |
[C] Q: scanf에서 오류가 나요. in Visual Studio (0) | 2018.12.30 |
[C] C99~C11 조금 낯선 기능들 (0) | 2018.12.30 |
[C] Q: 프로그램이 실행되자마자 꺼져요! (0) | 2018.12.30 |
글
[C] Q: scanf에서 오류가 나요. in Visual Studio
일단 이건 비주얼 스튜디오에서만 해당되는 문제입니다.
입력을 받는 간단한 프로그램을 하나 짜보도록 하죠.
1 2 3 4 5 6 7 8 9 10 11 12 | #include "stdafx.h" #include <iostream> int main() { char buffer[100]; printf("암거나 입력하세요 : "); scanf("%s", buffer); printf("결과 %s", buffer); } | cs |
VS에서는 이렇게 문법적으로 아무런 문제도 없는 코드를 짜도, 문제가 발생할 수 있습니다.
초심자들이 한번씩은 걸려드는 함정이죠.
자 오류가 떴습니다. 근데 대체 뭐라고 하는걸까요?
scanf 함수는 안전하지 않으니까 scanf_s를 쓰라고 하는 겁니다.
근데 그건 니들 생각이고, 나는 그걸 쓰고싶지 않아요.
1.define(안됐는데 알고보니 됨)
어쨌든 scanf를 쓰고싶으면 _CRT_SECURE_NO_WARNINGS라는 매크로를 정의하라고 하는데요. 그럼 이렇게 짜면 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include "stdafx.h" #include <iostream> #define _CRT_SECURE_NO_WARNINGS //잔소리좀 하지 말 int main() { char buffer[100]; printf("암거나 입력하세요 : "); scanf("%s", buffer); printf("결과 %s", buffer); } | cs |
이런 빌어먹을. 그래도 안된다고 합니다. 얘들이 왜이러지? 예전에는 이러면 됐었는데 뭔가 바뀌었나봅니다.
수정) 알고보니 define을 맨 위에 둬야한다네요. 잘못 씀.
2.pragma
다른 매크로를 써보죠, pragma란 것도 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include "stdafx.h" #include <iostream> #pragma warning(disable:4996) //좀 닥쳐봐 int main() { char buffer[100]; printf("암거나 입력하세요 : "); scanf("%s", buffer); printf("결과 %s", buffer); } | cs |
3.그냥 scanf_s를 쓸까?
근데 저놈들은 이게ㅔ 뭐라고 자꾸 쓰라고 강요를 하는 걸까요?
실제로 scanf에 취약점이 존재하기 때문입니다. 전달받은 변수보다 입력으로 들어오는 값이 더 크면 버퍼오버플로가 발생하는데, 이게 상당히 위험할 수 있거든요.
그래서 더 큰 값이 들어와도 잘라버릴 수 있도록 맨 마지막 인자로 변수의 크기를 넣어줍니다. '너무 커도 이 변수 크기까지만 넣어라!' 라는 뜻이죠.
이렇게요.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include "stdafx.h" #include <iostream> int main() { char buffer[100]; printf("암거나 입력하세요 : "); scanf_s("%s", buffer, sizeof(buffer)); //이제 됐냐? printf("결과 %s", buffer); } | cs |
잘 됩니다.
근데 사실 _s는 그렇게 좋은 기능만은 아닙니다. 이걸 이렇게 강력하게 지원하는 컴파일러가 사실상 이거밖에 없는데요...
굳이 이걸 쓰지 않아도 취약점을 막을 수 있는 방법이 많기 때문입니다.. 마지막 인자 때문에 추가되는 오버헤드도 있고요.
4.프로젝트 설정
코드에 손을 대고 싶지 않다면, 좀더 근본적으로 문제를 차단할 수도 있습니다.
자꾸 _s 버전 쓰라고 강요하는걸 SDL 체크라고 하는데요. 이걸 꺼버리면 됩니다.
솔루션 탐색기 창에서 프로젝트 파일에 마우스 오른쪽 버튼을 누르고
속성 창으로 들어갑니다.
그리고 C/C++ -> 일반 -> SDL 검사에서 '아니요'를 선택해주시고 확인 누르시면
잘 됩니다.
'언어 > C' 카테고리의 다른 글
[C] Q: 스택의 사이즈는 왜이렇게 작나요? (0) | 2018.12.30 |
---|---|
[C] ()와 (void)의 차이 (0) | 2018.12.30 |
[C] C99~C11 조금 낯선 기능들 (0) | 2018.12.30 |
[C] Q: 프로그램이 실행되자마자 꺼져요! (0) | 2018.12.30 |
[C] 함수 호출 규약.link (0) | 2018.12.17 |