-
Notifications
You must be signed in to change notification settings - Fork 4
/
memalloc.tex
947 lines (838 loc) · 39.6 KB
/
memalloc.tex
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
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
\chapter{Memory Allocation} \label{chap:memalloc}
% chapter 7: memalloc
많은 사람들이 C 언어에서 pointer가 가장 배우기 힘들다고 말합니다.
그러나 사실 이 말은 pointer보다 pointer가 가리키는 메모리를 관리하기가
힘들다는 뜻입니다. C 언어는 상대적으로 저수준의 언어이기 때문에,
프로그래머가 메모리를 직접 할당해야 합니다.
% TODO: 번역
% In keeping with C's low-level flavor, programmers are often reponsible
% for explicitly allocating memory, yet it is easy to overlook the allocation
% of pointed-to object.
제대로 할당되지 않은 메모리를 가리키는 포인터를 쓰는 것은 심각한 버그를
발생하는 가장 주된 원인이 됩니다.
\section{Basic Allocation Problems}
% from 7.1
여러분이 직접 \TT{malloc}을 부르지 않는다해도, 여러분이 쓰려고 하는 메모리가
제대로 초기화되었는지 검사해야 합니다.
\begin{faq}
\Q{7.1} % 7.1
이 코드는 왜 동작하지 않을까요?
\begin{verbatim}
char *answer;
printf("Type something:\n");
gets(answer);
printf("You typed \"%s\"\n", answer);
\end{verbatim}
\A
\TT{gets()}에 전달된 포인터 변수인 \TT{answer}는 문자열이
저장될 어떤 공간을 가리키고 있어야 합니다. 위의 코드에서는
\TT{answer}가 어디를 가리키는 지 알 수 없습니다. 즉, 초기화되지
않는 변수이기 때문에 다음 코드와 같이 잘못된 코드입니다:
\begin{verbatim}
int i;
printf("i = %d\n", i);
\end{verbatim}
따라서 주어진 코드의 첫 줄에서 우리는 \TT{answer}가 어디를
가리키는지 알 수 없습니다. 즉, 바로 위의 코드에서 \TT{i}가
무슨 값을 가지는지 알 수 없는 것과 같은 이유입니다.
(초기화되지 않은 지역 변수\footnote{uninitialized local variable}는
일반적으로
쓰레기 값을 가지게 됩니다. 이는 널 포인터와는 다릅니다. 질문
\ql{1.30}, \ql{5.1}을 참고하기 바랍니다)
위와 같이, 간단히 질문하고 답변받는 프로그램에서는 포인터 대신
지역 배열을 쓰는 것이 훨씬 간단합니다:
\begin{verbatim}
#include <stdio.h>
#include <string.h>
char answer[100], *p;
printf("Type something:\n");
fgets(answer, sizeof answer, stdin);
if ((p = strchr(answer, '\n')) != NULL)
*p = '\0';
printf("You typed \"%s\"\n", answer);
\end{verbatim}
\noindent 이 예제는 \TT{gets()} 대신 \TT{fgets()}를 썼습니다.
따라서 배열의 끝이 덮어쓰여질 염려가 없습니다. (질문 \ql{12.23}을
참고하기 바랍니다. 아쉽게도 이 예에서 쓴 \TT{fgets()}는
\TT{gets()}와 달리 마지막 \verb+\n+을 지우지 못합니다.)
\TT{answer}에
\TT{malloc()}으로 메모리를 할당하고, 버퍼 크기를 다음과 같이
파라메터로 넘기는 방법도 있습니다.
\begin{verbatim}
#define ANSWERSIZE 100
\end{verbatim}
\end{faq}
\begin{faq}
\Q{7.2} % 7.2 COMPLETE
\TT{strcat()}이 동작하지 않습니다. 코드는 다음과 같으며:
\begin{verbatim}
char *s1 = "Hello, ";
char *s2 = "world!";
char *s3 = strcat(s1, s2);
\end{verbatim}
\noindent 이상한 결과가 발생합니다.
\A
질문 \ql{7.1}에서 언급한 것처럼, 주 원인은 연결한 문자열을 저장할만한
공간이 없다는 것입니다. C 언어는 동적으로 문자열을 처리해주지
않습니다.
C 컴파일러는 소스에서 요구한 만큼만 메모리를 할당합니다 (문자열의
경우엔 문자 배열과 문자열 상수를 포함합니다). 프로그래머는
문자열 연결(concatenation)과 같은 실행 시간 연산(run-time operation)에
필요한 적절한 공간을 배열이나 \TT{malloc()}을 불러서 직접 만들어
주어야 합니다.
\TT{strcat()}은 어떠한 공간도 할당해주지 않습니다; 두번째 문자열은
단순히 첫번째 문자열에 붙어서 연결됩니다. 그러므로 첫번째 문자열을
저장하는 곳이 충분한 공간을 가지고 있어야 하며, 쓸 수 있어야(writable)
합니다. 따라서 다음과 같이 배열로 선언하면 쉽습니다:
\begin{verbatim}
char s1[20] = "Hello, ";
\end{verbatim}
물론, 실전에 쓰기 위해서는 위와 같이 20이라는 상수를 쓰는 것은
좋지 않습니다. 우리는 적절한 공간을 보장할 수 있는 어떠한 메커니즘을
써서 공간을 할당해야 합니다.
\noindent \TT{strcat()}이 첫번째 문자열을 가리키는 (질문에서는
\TT{s1}) 포인터를 리턴하므로, 변수 \TT{s3}는 불필요합니다;
\TT{strcat()}을 부른 다음에 결과는 \TT{s1}이 가리키고 있습니다.
질문에서 \TT{strcat()}을 부른 코드는 크게 두가지 문제를 가지고
있습니다:
먼저 \TT{s1}이 충분한 공간을 가지고 있지 않다는 것과, 이 문자열이
쓸 수 없는(read-only) 문자열이라는 것입니다. 질문 \ql{1.32}를 참고하기
바랍니다.
\R
\cite{ctp} \S\ 3.2 \page{32}
\end{faq}
\begin{faq}
\Q{7.3} % 7.3 COMPLETE
그러나 매뉴얼(man) 페이지는 \TT{strcat()}이 인자로 두 개의
\verb+char *+를 받는다고 씌여 있습니다. 어떻게 이 것만 가지고
필요한 메모리 공간을 만들어 주어야 한다는 것을 알 수 있죠?
\A
일반적으로 포인터를 사용할 때에는 항상 메모리 공간을 잡아 주어야
한다고 생각해야 합니다. 만약 라이브러리 함수의 설명에 메모리 할당에
대한 언급이 없다면, 대부분은 호출하는 쪽에서 알아서 해 주어야 합니다.
UNIX 스타일의 매뉴얼(man) 페이지의 첫 부분에 있는 `Synopsis' 섹션,
또는 ANSI C 표준에 나온 이러한 부분은 잘못 이해하기 쉽습니다.
이들 문서에 나온 코드는 호출하는 입장이 아닌 함수를 만드는 입장에
더 가깝기 때문입니다. 특히 포인터를 인자로 받는 (구조체나 문자열)
대부분의 함수들은 포인터가, 어떤 오브젝트를 (구조체나 배열 --- 질문
\ql{6.3}, \ql{6.4} 참고) 가리켜야 하는 경우가 많으며, 이 오브젝트는 부르는
입장에서 할당해 주어야 하는 경우가 대부분입니다. 다른 예로는
\TT{time()} 함수와 (질문 \ql{13.12} 참고) \TT{stat()} 함수를 들 수 있습니다.
\end{faq}
\begin{faq}
\Q{7.3b} % 7.3b
다음과 같은 코드를 실행했는데:
\begin{verbatim}
char *p;
strcpy(p, "abc");
\end{verbatim}
\noindent 동작을 합니다. 왜 그러죠? 제 예상대로라면 프로그램이
망가져야(crash) 하는데요.
\A
추측컨대 아주 재수가 좋은 모양입니다. 초기화되지 않은 포인터 \TT{p}에
어떤 쓰레기 값이 들어갔고, 그 값이 여러분이 쓸 수 있는 메모리 공간을
가리키고 있었고, 그 공간이 어떤 중요한 목적으로 쓰이고 있지 않았기
때문입니다.
\end{faq}
\begin{faq}
\Q{7.3c} % 7.3c
포인터 변수는 얼마나 큰 메모리를 할당할까요?
\A
아주 잘못된 질문입니다. 포인터 변수를 다음과 같이 선언했다고 할 때:
\begin{verbatim}
char *p;
\end{verbatim}
\noindent 여러분은 (좀더 정확히 말해서, 컴파일러는) 포인터 자체만
저장할 수 있는 공간을 할당한 것입니다; 즉, 이 경우
\verb+sizeof(char *)+ 바이트만큼의 메모리가 할당된 것입니다.
그러나 이 포인터는 아직 어떠한 메모리도 가리키고 있지 않습니다.
질문 \ql{7.1}과 \ql{7.2}를 참고하기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.4} % 7.4 COMPLETE
다음과 같이 파일에서 줄 단위로 읽는 코드를 만들었습니다:
\begin{verbatim}
char linebuf[80];
char *lines[100];
int i;
for (i = 0; i < 100; i++) {
char *p = fgets(linebuf, 80, fp);
if (p == NULL) break;
lines[i] = p;
}
\end{verbatim}
그런데 이 코드를 실행하면, 모든 줄에 마지막 줄의 내용이 들어가 있습니다.
\A
여러분이 선언한 \TT{linebuf}는 단지 한 줄만을 저장할 수 있는
버퍼입니다. \TT{fgets}를 부를 때마다, 기존의 줄은 덮어써져 버립니다.
\TT{fgets}는 내부적으로 메모리를 할당해 주지 않습니다.
에러가 났거나 \TT{EOF}를 만나지 않는다면, \TT{fgets}가 리턴하는
값은 여러분이 첫번째 인자로 전해 준 포인터와 같습니다.
(이 경우 \TT{linebuf}를 가리키는 포인터)
이런 식으로 코드를 작성하려 한다면, 여러분이 각각의 줄을 저장할
공간을 일일이 할당해 주어야 합니다.
질문 \ql{20.2}의 코드를 참고하기 바랍니다.
\R
\cite{kr1} \S\ 7.8 \page{155} \\
\cite{kr2} \S\ 7.7 \Page{164--5}
\cite{ansi} \S\ 4.9.7.2 \\
\cite{c89} \S\ 7.9.7.2 \\
\cite{hs} \S\ 15.7 \page{356}
\end{faq}
\begin{faq}
\Q{7.5a}
문자열을 리턴하는 함수를 만들었는데, 리턴한 문자열이
쓰레기로 채워진 것 같습니다. 왜 그럴까요?
\A
포인터가 적절한 메모리 공간을 가리키고 있는지 잘 검사해보시기
바랍니다. 함수가 리턴하는 포인터는 정적으로 할당된 버퍼이거나,
이 함수를 부른 위쪽에서 전달해 준 버퍼이거나, 또는 \TT{malloc}으로
할당한 버퍼 등을 가리키고 있어야 합니다. 그러나 절대로 이 함수
안에서 할당한 지역 변수면 (automatic array) 안됩니다.
예를 들어 절대로 다음과 같이 하지 말기 바랍니다:
\begin{verbatim}
#include <stdio.h>
char *itoa(int n)
{
char retbuf[20]; /* WRONG */
sprintf(retbuf, "%d", n);
return retbuf; /* WRONG */
}
\end{verbatim}
함수가 끝나면, 함수 안에서 만들어진 automatic, local 변수는 없어집니다.
따라서 위 경우는 올바른 코드가 아닙니다 (즉, 리턴되는 포인터가 존재하지
않는 곳을 가리키고 있습니다).
한 가지 방법은 (완전한 것은 아닙니다. 특히 이 함수가 재귀적으로
호출되거나 동시에 이 함수를 여러번 부르고, 그 값을 사용하려 할 때에는
쓸 수 없습니다.) 리턴할 버퍼를 다음과 같이 만드는 것입니다:
\begin{verbatim}
static char retbuf[20];
\end{verbatim}
다른 방법은, 이 함수를 부르는 함수가, 버퍼를 제공하도록 고치는
것입니다:
\begin{verbatim}
char *itoa(int n, char *retbuf)
{
sprintf(retbuf, "%d", n);
return retbuf;
}
...
char str[20];
itoa(123, str);
\end{verbatim}
또, \TT{malloc}을 쓰는 방법도 있습니다:
\begin{verbatim}
#include <stdlib.h>
char *itoa(int n)
{
char *retbuf = malloc(20);
if (retbuf != NULL)
sprintf(retbuf, "%d", n);
return retbuf;
}
...
char *str = itoa(123);
\end{verbatim}
\noindent 이 경우, 이 함수를 부르는 쪽에서 더 이상 이 함수가 리턴한
값이 필요없을 경우, 리턴한 포인터가 가리키고 있는 메모리를 free해
주어야 합니다.
\seealso{\ql{7.5b}, \ql{12.21}, \ql{20.1}}
\R
\cite{ansi} \S\ 3.1.2.4 \\
\cite{c89} \S\ 6.1.2.4
\end{faq}
\begin{faq}
\Q{7.5b} % 7.5b
그럼 문자열이나 기타 이런 것들을 리턴하려면 어떻게 해야
하죠?
\A
포인터를 리턴할 때에는 이 포인터가 정적으로 할당된
(statically-allcated) 공간을 가리키고 있어야 합니다.
또는 이 버퍼가 이 함수에 전달된 메모리를 가리키고 있거나, 이 버퍼가
\TT{malloc()}으로 할당된 버퍼이어야 합니다. 절대로 지역(local,
automatic) 배열을 가리키고 있어서는 안됩니다.
질문 \ql{20.1}을 참고하기 바랍니다.
\end{faq}
\section{Calling \TT{malloc}}
고정된 메모리 블럭을 넘어서, 좀 더 많은 유연성을 필요로 한다면, 메모리를
동적으로 할당하는 방식을 쓸 단계입니다. 대개는 \TT{malloc} 함수를 써서 이
작업을 수행합니다.
이 section에서는 \TT{malloc}에 대한 기본적인 사항에 대하여 다루며,
다음 section에서는 \TT{malloc}이 실패했을 경우에 대한 것을 배웁니다.
\begin{faq}
\Q{7.6} % 7.6 COMPLETE
\TT{malloc()}을 호출할 때 왜 ``warning: assignment of pointer
from integer lacks a cast''라는 경고가 발생할까요?
\A
\verb+<stdlib.h>+를 포함하거나, \TT{malloc}을 쓰기 위해 올바른
선언을 제공했는지 확인하기 바랍니다. 만약 이런 일을 안했다면,
컴파일러는 \TT{int}를 리턴하는 것으로 (질문 \ql{1.25} 참고) 가정합니다.
물론 이 가정은 잘못된 것입니다 (같은 이유로, 질문한 경고 메시지가
\TT{calloc}이나 \TT{realloc}에서 발생할 수 있습니다.
\seealso{\ql{7.15}}
\R
\cite{hs} \S\ 4.7 \page{101}
\end{faq}
\begin{faq}
\Q{7.7} % 7.7
어떤 코드를 보면 \TT{malloc()}이 리턴한 포인터를 대입할 포인터의
타입으로 캐스팅한 것을 볼 수 있는 데, 왜 그럴까요?
\A
ANSI/ISO C 표준에서 \TT{void *}를 소개하기 전에는, 대개 포인터
변환에 관계된 경고를 없애기 위해, 또는 불필요한 변환을 줄이기 위해
이러한 캐스팅을 사용했습니다.
ANSI/ISO C 표준에서는 이러한 캐스팅이 전혀 필요없습니다.
그리고 현재 이런 캐스팅을 사용하는 것은 나쁜 프로그래밍 스타일로
간주되기도 합니다. 왜냐하면 \TT{malloc()}이 선언되지 않았을 때
발생할 수 있는 유용한 경고 메시지를 발생시키지 않기 때문입니다;
질문 \ql{7.6}을 참고하기 바랍니다. (그러나 이런 캐스팅은 여전히
자주 쓰이고 있습니다. 왜냐하면 C++에서는 이러한 캐스팅이 반드시
필요하기 때문에, 호환성을 유지하기 위해서입니다.)
\R
\cite{hs} \S\ 16.1 \Page{386--7}
\end{faq}
\begin{faq}
\Q{7.8}
다음과 같은 코드를 본 적이 있습니다:
\begin{verbatim}
char *p = malloc(strlen(s) + 1);
strcpy(p, s);
\end{verbatim}
\noindent 제 생각에는 \verb?malloc((strlen(s) + 1) * sizeof(char))?로
되어야 할 것 같은데요.
\A
\verb+sizeof(char)+로 곱하는 것은 전혀 필요 없습니다.
왜냐하면 정의에 의해, \verb+sizeof(char)+는 항상 1이기 때문입니다.
다른 말로 하면 \verb+sizeof(char)+를 곱하는 것은 전혀 문제가
되지 않습니다. 1을 곱하는 것은 아무런 영향을 끼치지 않기 때문입니다.
추가적으로 \verb+size_t+ 타입을 쓰는 것이 도움이 될 때도 있습니다.
(질문 \ql{7.15} 참고) \seealso{\ql{8.9}, \ql{8.10}}
\R
\cite{ansi} \S\ 3.3.3.4 \\
\cite{c89} \S\ 6.3.3.4 \\
\cite{hs} \S\ 7.5.2 \page{195}
\end{faq}
\begin{faq}
\Q{7.9} % COMPLETE
다음과 같이 \TT{malloc()}의 wrapper를 만들었습니다. 그런데 왜 동작하지
않을까요?
\begin{verbatim}
#include <stdio.h>
#include <stdlib.h>
mymalloc(void *retp, size_t size)
{
retp = malloc(size);
if (retp == NULL) {
fprintf(stderr, "out of memory\n");
exit(EXIT_FAILURE);
}
}
\end{verbatim}
\A
질문 \ql{4.8}을 참고하기 바랍니다. (이 경우, 여러분은 \TT{mymalloc}이 할당된
공간을 가리키는 포인터를 돌려주도록 해야 합니다.)
% TODO: ()안의 말을 좀 더 다듬기..
\end{faq}
\begin{faq}
\Q{7.10} % COMPLETE
포인터를 선언하고 여기에 공간을 할당하려고 합니다. 그런데 제대로
동작하지 않는군요. 다음 코드에서 뭐가 잘못되었나요?
\begin{verbatim}
char *p;
*p = malloc(10);
\end{verbatim}
\A
질문 \ql{4.2}를 보기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.11} % COMPLETE
배열을 동적으로 할당할 수 있을까요?
\A
질문 \ql{6.14}와 \ql{6.16}을 보기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.12} % COMPLETE
쓸 수 있는 메모리 공간이 얼마나 남아 있는지 알 수 있을까요?
\A
질문 \ql{19.22}를 보기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.13} % COMPLETE
\TT{malloc(0)}은 어떤 값을 리턴하는 거죠? 널 포인터를 리턴하나요,
0 바이트를 가리키는 포인터를 리턴하나요?
\A
질문 \ql{11.26}을 보기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.14} % COMPLETE
어떤 시스템에서는 \TT{malloc()}으로 메모리를 할당해도
프로그램에서 이 메모리에 접근하기 전에는 운영체제가 메모리를
할당하지 않는다고 들었습니다. 그래도 상관없을까요?
\A
꽤 말하기 어려운 문제입니다. 표준에서는 이런 식으로 동작하는
시스템이 있다고 말한 바가 없습니다. 하지만, 반대로 그러한 시스템이
없다고말한 바도 없습니다. (이와 같은 ``deferred failure''
implementation은 표준이 요구하는 것과 상관없어 보입니다.)
실제 메모리에 쓰려고(write) 할 때, 남아 있는 메모리가 없는 경우가
문제가 되는데, C 언어 semantic에는 recourse가 없으므로,
이 경우, 대개 OS가 해당 프로그램을 죽이게(kill) 됩니다.
% TODO: semantics of C provide no recource.
(당연히, \TT{malloc}은 메모리가 없을 경우, 널 포인터를 리턴하게 되므로,
\TT{malloc}의 리턴 값을 확인했다면, 할당한 메모리가 아닌 다른 메모리에
접근할 까닭이 없습니다.)
이와 같이 ``lazy allocation'' 방식을 쓰는 시스템에서는 대개 메모리가
부족하다는 것을 알려주는 시그널을 제공합니다. 그러나 프로그램에서
이 시그널을 처리하려면 이식성이 떨어지는 프로그램이 될 가능성이
높습니다. 이 방식을 제공하는 시스템 중에는 사용자 또는 프로세스 단위로
이 기능을 끄는(off) 기능을
제공하는 (즉, 전형적인 \TT{malloc} semantic을 쓰는) 것도 있습니다만,
시스템마다 매우 달라집니다.
\R
\cite{ansi} \S\ 4.10.3 \\
\cite{c89} \S\ 7.10.3
\end{faq}
\section{Problems with \TT{malloc}}
\begin{faq}
\Q{7.15} % COMPLETE
왜 \TT{malloc}이 이상한 값을 리턴할까요? 질문 \ql{7.6}을 읽고나서
\TT{malloc}을 부르기 전에 \TT{extern void *malloc();} 선언을
포함시켰는데도 이상한 쓰레기 값을 줍니다.
\A
\TT{malloc}의 인자 타입은 \verb+size_t+이며, 아마 \TT{unsigned long}
타입일 가능성이 높습니다. 만약에 \TT{int}
타입(또는 \TT{unsigned int})을
전달했다면, \TT{malloc}이 (깨진) 쓰레기 값을 받았을 가능성이 있습니다.
(또는 \TT{long} 타입을 전달했는데 \verb+size_t+가 \TT{int}일 경우에도
이럴 가능성이 있습니다.)
일반적으로, 표준 라이브러리 함수의 선언을 \TT{extern}으로 직접 써 주는
것보다 알맞는 헤더 파일을 포함시키는 것이 훨씬 더 안전합니다.
\seealso{\ql{7.16}}
비슷하면서 잘 알려진 또 다른 문제는, \verb+size_t+ 타입의 값을
(\TT{sizeof}의 값 포함) \TT{printf}의 \TT{\%d}를 써서 출력하는
것입니다. 이식성이 높은 코드를 얻기 위해서, 주어진 값을 직접
(\TT{unsigned long}으로) 캐스팅한 다음 \TT{\%lu} 포맷으로 출력하는 것이
좋습니다.
\begin{verbatim}
printf("%lu\n", (unsigned long)sizeof(int));
\end{verbatim}
\seealso{\ql{15.3}}.
\R
\cite{ansi} \S\ 4.1.5, \S\ 4.1.6 \\
\cite{c89} \S\ 7.1.6, \S\ 7.1.7
\T
C99 표준은 \verb+size_t+를 쉽게 출력하기 위해, \TT{printf},
\TT{scanf} 함수들에 \TT{z} modifier를 추가했습니다.
질문 \ql{13.1}을 참고하기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.16} % COMPLETE
어떤 수학 계산을 하기 위해 꽤 큰 배열이 필요해서 다음과
같이 코드를 작성했습니다:
\begin{verbatim}
double *array = malloc(256 * 256 * sizeof(double));
\end{verbatim}
\noindent 이 때 \TT{malloc()}은 널 포인터를 리턴하지 않았지만
프로그램이 매우 이상하게 동작합니다. 제 생각에는 메모리를 겹쳐
쓰는(overwrite) 현상이 발생한 것 같거나 \TT{malloc()}이 원하는 만큼의
큰 메모리를 할당한 것 같지가 않습니다.
\A
256 곱하기 256은 65,536이며, \TT{int}가 16-bit인 경우, 저장될 수 없는,
큰 값입니다. 게다가 이 수치는
다시 \TT{sizeof(double)}로 곱해야 하기 때문에 상당히 큰 크기입니다.
이런 큰 배열이 꼭 필요하다면 꽤 주의를 기울여야 합니다.
만약 여러분의 시스템에서 \verb+size_t+ 타입이 (\TT{malloc()}이 인자로 받는
타입) 32 비트라면, 그리고 \verb+int+가 16 비트라면,
위 코드에서 \TT{malloc}의 인자로 \TT{256 * (256 * sizeof(double))}를
써서 해결할 가능성도 있습니다. (질문 \ql{3.14} 참고).
그렇지 않다면 여러분은 이 메모리를 작은 크기로 쪼개어 쓰거나,
또는 32 비트 컴퓨터나 컴파일러를 쓰거나, 비표준으로 제공되는
메모리 할당 함수를 써야 할 것입니다. \seealso{\ql{7.15}, \ql{19.23}}
\T
이 질문은 16-bit 시스템 및 컴파일러가 대부분일 시절에 만들어진 것이기
때문에, 현실과 거리가 멀 수도 있지만, 비슷한 이유로 32-bit 시스템에서도
문제가 발생할 수 있으므로 그냥 두겠습니다.
\end{faq}
\begin{faq}
\Q{7.17} % COMPLETE
제 PC는 8 메가 바이트의 메모리를 보유하고 있습니다.
그런데 왜 640K의 크기 밖에 쓸 수 없는 것일까요?
\A
PC 호환의 세그먼트(segment) 구조를 사용하는 컴퓨터에서
640K 이상의 메모리를 사용하는 것은 (특히 MS-DOS에서) 매우 어렵습니다.
질문 \ql{19.23}을 참고하기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.18}
제 프로그램은 어떤 노드(어떤 자료 구조)를 동적으로 할당하는 작업을
매우 많이 수행합니다. 테스트 결과 \TT{malloc/free}에서 발생하는
오버헤드가 bottleneck이 되고 있는데, 좋은 방법이 있을까요?
\A
만약 주어진 노드의 크기가 모두 같다면, \TT{free}를 부르는 대신에
개발자가 직접 쓰이지 않는 노드로 이루어진 리스트를 관리하는 것이
좋을 수 있습니다. (이 방식은, 프로그램에서 가장 많은 메모리를
소비하는 것이
바로 이 노드일 때 좋은 해결책이 됩니다. 그렇지 않다면 이 노드 리스트에
묶여 있는 메모리가 많아서 다른 곳에서 메모리를 쓸 수 없게 되어
버립니다.)
\T
사용자가 직접 죽은(dead) 노드들을 리스트에 저장해 두고, 필요할 때,
\TT{malloc}을 부르는 대신 이 죽은 노드를 활용하는 것을,
`object caching'이라고 부르기도 합니다.
예를 들어, 리스트의 노드가 다음과 같을 경우:
\begin{verbatim}
struct node {
struct node *next;
...
};
\end{verbatim}
그리고, 새 노드를 만드는 함수와 필요없는 노드를 삭제하는 함수가
다음과 같다고 가정합시다:
\begin{verbatim}
struct node *new_node(void)
{
struct node *p;
p = malloc(sizeof(*p));
/* check if malloc() returns a null pointer */
...
return p;
}
void delete_node(struct node *p)
{
...
free(p);
}
\end{verbatim}
\noindent 이 경우, 다음과 같은 전역 변수를 두고,
\begin{verbatim}
static struct node *grave = 0;
\end{verbatim}
\noindent 위에서 만든 두 함수를 ``object caching'' 기법을 쓰도록
바꿀 수 있습니다:
\begin{verbatim}
struct node *new_node(void)
{
struct node *p;
if (grave) {
p = grave;
grave = p->next;
}
else {
p = malloc(sizeof(*p));
/* check if malloc() returns a null pointer */
}
...
return p;
}
void delete_node(struct node *p)
{
...
p->next = grave;
grave = p;
}
\end{verbatim}
\noindent 물론 수정된 두 함수가 완벽하지는 않습니다.
\verb+delete_node()+는 \TT{free}를 부르지 않고, 필요없는 모든
\TT{node}를 \TT{grave} 포인터에 리스트로 보관하고, 필요한 경우,
(즉 \verb+new_node()+를 불렀을 경우) 리스트에 보관된 노드를 다시
씁니다. 최적화를 위해서, 일정한 갯수만 \TT{grave} 리스트에 보관하고,
나머지는 \TT{free}를 불러서 해제하는 방식으로도 만들 수 있습니다.
이런 ``object caching''이 모든 문제를 해결해 주지는 않습니다. 상황에
따라, 이 방식이 도움이 될 경우가 있으므로, 여기에서 소개한 것이지,
항상 좋다는 것은 아니라는 것을 기억하시기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.19}
제 프로그램은 \TT{malloc()} 내부에서 오류가 발생한 것
같습니다. 그런데 제가 보기에는 제 프로그램에 잘못된 부분이 없는
것 같습니다. \TT{malloc()} 내부에 버그가 있는 게 아닐까요?
\A
불행히도, 프로그래머가 \TT{malloc()}이 내부적으로 유지하는
데이터 구조를 망가뜨리는 경우가 자주 발생합니다. 또 그 결과 생기는
문제는 매우 다루기가 어렵습니다. 가장 흔히 발생하는 실수는,
\TT{malloc()}으로 할당한 메모리보다 더 많은 데이터를 쓰려고(write)
하는 경우입니다; 특히 문자열을 저장하기 위해 \verb2strlen(s) + 12을
쓰지 않고 \TT{malloc(strlen(s))}를 쓰는 경우가 흔합니다.\footnote{또
자주 발생하는 다른 버그로, \TT{malloc(strlen(s + 1))}로 쓰는 경우와
\TT{p = malloc(sizeof(p))}를 쓰는 경우가 있습니다.}
다른 문제로 이미 \TT{free()}를 불러서 반환한 메모리 블럭에
쓰려고(write) 하거나 (질문 \ql{7.30} 참고),
\TT{free()}를 같은 포인터에 대해 두 번 호출하는 경우도
있습니다. (이 중 몇가지는 표준에 의해 인정되고 있습니다. ANSI
호환 시스템에서 크기가 0인 메모리 블럭을 \TT{malloc}, \TT{realloc}
등으로
할당하거나, 널 포인터를 \TT{free()}로 반환하는 것은 아무런 문제도
일으키지 않습니다. 물론 오래된 시스템에서는 문제가 됩니다.)
대개 이러한 실수는 금방 발견되지 않고, 오랫동안 숨어 있거나,
또는 전혀 상관없는 코드 부분에서 에러가 발생한 것처럼 보이기도 해서
수정하기가 매우 까다롭습니다.
대부분 \TT{malloc} implementation은 중요한 정보를 할당한 메모리 바로
옆에 보관하기 때문에, 사용자가 포인터를 조금이라도 잘못 쓰게 되면,
심각한 문제가 발생합니다.
\seealso{\ql{7.15}, \ql{7.26}, \ql{16.8}, \ql{18.2}}
\end{faq}
\section{Freeing Memory}
% from 7.20
Memory allocated with \TT{malloc} can persist as long as you need it.
It is never deallocated automatically (except when your program exits; see
question \ql{7.24}). When your program uses memory on a transient basis, it
can---and should---recycle it by calling \TT{free}.
\begin{faq}
\Q{7.20} % COMPLETE
동적으로 할당한 메모리를 해제한 다음에는 다시 쓸 수 있나요?
\A
쓸 수 없습니다. 어떤 오래된 \TT{malloc()}에 대한 문서는
해제된 메모리는
변경되지 않는 상태로 남아 있다고 (left undisturbed) 씌여 있지만
이는 잘못된 것이며, C 표준에도 언급되지 않은 사항입니다.
대부분의 프로그래머들이 반환된 메모리의 내용을 일부러 다시 쓰지는
않습니다만, 실수로 반환한 메모리 공간을 쓰기도 합니다.
다음에 나온 singly-linked 리스트를 반환하는 코드를
보시기 바랍니다:
\begin{verbatim}
struct list *listp, *nextp;
for (listp = base; listp != NULL; listp = nextp) {
nextp = listp->next;
free(listp);
}
\end{verbatim}
\noindent 만약 위 코드가 임시 변수 \TT{nextp}를 쓰지 않고,
\verb+listp = listp->next+를 썼다면, 이미 반환한 메모리를
\verb+listp->next+로 다시 접근하려 한다는 것을 알 수 있습니다.
\R
\cite{kr2} \S\ 7.8.5 \page{167} \\
\cite{ansi} \S\ 4.10.3 \\
\cite{c89} \S\ 7.10.3 \\
\cite{rationale} \S\ 4.10.3.2 \\
\cite{hs} \S\ 16.2 \page{387} \\
\cite{ctp} \S\ 7.10 \page{95}
\end{faq}
\begin{faq}
\Q{7.21} % COMPLETE
왜 \TT{free()}를 부른 다음에 포인터가 널이 되지 않는
걸까요?
\A
\TT{free()}를 부르면 이 함수에 전달된 포인터가 가리키고 있던
메모리가 해제됩니다. 그러나 이 포인터 자체의 값은 변경되지 않고
남아있습니다. 왜냐하면 C 언어는 인자를 전달할 때, `pass-by-value'
개념을 쓰기 때문입니다. 따라서 함수가 (이 경우 \TT{free()}) 인자로
전달된 변수의 값을 변경할 수 없습니다. (질문 4.8을 참고하기
바랍니다.)
일단 해제된 포인터 값은 엄밀히 말해서, 유효하지 않습니다(invalid).
그리고 (dereference가 아니더라도) 어떠한 목적으로 (심지어,
단순히 대입하거나 비교하는 것도) 이 값을 쓰는 것은,
물론 구현 방법에 따라 다르긴 하지만, 이론상 문제를 발생할 수 있습니다.
(물론, 대부분의 시스템이 문제가 없어 보이는
invalid 포인터 쓰임새를 너그럽게 봐 주지만, 표준은 확실하게,
어떤 것도 보장될 수 없다고 말합니다. 또한 어떤 시스템은 구조상 이런
exception 상황이 흔히 발생합니다.)
\R
\cite{ansi} \S\ 4.10.3 \\
\cite{c89} \S\ 7.10.3 \\
\cite{rationale} \S\ 3.2.2.3
\end{faq}
\begin{faq}
\Q{7.22}
함수에 속한 지역(local) 포인터에 \TT{malloc()}으로
메모리를 할당했을 때에도 반드시 \TT{free()}를 해주어야 하나요?
\A
당연합니다. 포인터와 포인터가 가리키는 메모리는 서로 다른 것이라는
것을 기억해야 합니다. 함수가 끝났을 때, 지역 변수로 선언한 포인터는
자동으로 해제되지만, 포인터 자체가 해제된다는 뜻이지, 포인터가
가리키는 메모리 블럭이 해제된다는 것은 아닙니다.
\TT{malloc()}으로 할당한 메모리는 \TT{free()}를 써서 해제하기 전에는
메모리에 남아 있습니다. 일반적으로 모든 \TT{malloc()}에는 각각의
호출에 해당하는 \TT{free()}를 만들어 주어야 합니다.
\end{faq}
\begin{faq}
\Q{7.23}
동적으로 할당한 어떤 오브젝트를 가리키는 포인터를 포함하는
구조체를 할당했습니다. 이 구조체를 해제할 때, 각각의 포인터 멤버가
가리키는 메모리도 따로 해제해 주어야 하나요?
\A
그렇습니다. \TT{malloc()}으로 할당한 메모리는 각각, 정확히
딱 한번, \TT{free()} 해주어야 합니다. 프로그램에서 각각
\TT{malloc()}으로 할당한 메모리는 모두 \TT{free()}시키는 것이
좋은 습관입니다.
질문 \ql{7.24}를 참고하기 바랍니다.
\end{faq}
\begin{faq}
\Q{7.24}
프로그램이 끝나기 전에 동적으로 할당한 메모리는
반드시 해제시켜야 하나요?
\A
그럴 필요는 없습니다. 일반적으로 운영체제는 프로그램이 끝났을 때,
프로그램이 할당한 모든 메모리를 해제시켜 줍니다. 그럼에도
불구하고, 어떤 PC에서는 메모리를 제대로 복원시키지 못한다고
알려져 있습니다. ANSI/\cite{c89} C 표준에서는 이런한 상황을
`구현 수준에 따른 상황(quality of implementation issue)'이라고
말하고 있습니다.
\R
\cite{c89} \S\ 7.10.3.2 \\
\end{faq}
\begin{faq}
\Q{7.25}
제 프로그램은 메모리 블럭을 \TT{malloc()}으로 할당하고,
나중에 \TT{free()}시키는 구조를 가집니다. 그런데, 프로그램이 끝난
다음에도, 운영 체제의 메모리 상태를 살펴보면 전혀 줄어들지 않는
것을 볼 수 있습니다.
\A 대부분의 \TT{malloc/free}의 구현 방법들은 메모리를 운영체제에
즉시 반환하는 것이 아니며, 단지 그 프로그램에서 나중에 나올
\TT{malloc()}이 그 메모리를 다시 쓸 수 있도록 해 주도록 되어
있습니다.
\end{faq}
\section{Sizes of Allocated Blocks}
%
%
\begin{faq}
\Q{7.26}
\TT{free()} 함수는 어느 정도 크기의 메모리를 해제할 지
어떻게 알 수 있는 것일까요?
\A
\TT{malloc/free} 구현은 동적으로 할당한 각각의 메모리 블럭의 크기를
내부적으로 유지하고 있습니다. 따라서 \TT{free()}를 쓸 때에
그 크기를 지정하지 않아도 자동으로 그 크기를 알 수 있습니다.
\end{faq}
\begin{faq}
\Q{7.27}
그렇다면, 할당한 블럭의 크기가 어느 정도인지 \TT{malloc}
패키지를 써서 알 수 있을까요?
\A
불행하게도, 동적 블럭의 크기를 알 수 있는 표준 또는 호환성있는
방법은 제공되지 않습니다.
\end{faq}
\begin{faq}
\Q{7.28}
어떤 포인터가 메모리를 가리키고 있을 때, 왜 \TT{sizeof}는 그 메모리
블럭의 크기를 알려주지 않나요?
\A
\TT{sizeof} 연산자는 포인터가 가리키는 값이 \TT{malloc}으로 할당한
메모리를 가리키고 있는지 알지 못합니다. \TT{sizeof}가 알려주는 것은
포인터 자체가 차지하고 있는 크기입니다. \TT{malloc}을 불러서 얻은
메모리 블럭의 크기를 알아내는 (이식성이 높은) 방법은 없습니다.
\end{faq}
\section{Other Allocation Functions}
% TODO
\begin{faq}
\Q{7.29}
(질문 \ql{6.14}를 따라서) 배열을 동적으로 할당한 다음, 이 배열의 크기를
바꿀 수 있을까요?
\A
물론입니다. \TT{realloc}이 바로 그 역할을 해 주는 함수입니다.
(예를 들어, 질문 \ql{6.14}에 나온 것처럼 \TT{dynarray}) 동적으로 할당된
배열의 크기를 바꾸려면 다음과 같은 코드를 씁니다:
\begin{verbatim}
dynarray = (int *)realloc((void *)dynarray,
20 * sizeof(int));
\end{verbatim}
\TT{realloc}이 항상 메모리 블럭의 크기를 늘리는데\footnote{또는 항상
크기를 줄이는데에만 쓰이는 것도 아닙니다.} 쓰이는 것은 아닙니다.
\TT{realloc}은, 가능하면, 전달받은 인자와 같은 포인터 값을 돌려주지만,
요청한 크기에 맞게 메모리 블럭을 다시 찾을 경우에는, 인자로 전달된
포인터 값과는 다른 값을 돌려줍니다. 이 경우, 인자로 전달된 포인터 값은
더 이상 쓸 수 없습니다.
만약 \TT{realloc}이 요청한 메모리 공간을 찾지 못했다면, 널 포인터를
리턴합니다. 이 때, 인자로 전달되었던 메모리는 (\TT{realloc}을 부르기
바로 전 상태로) 그대로 유지됩니다.\footnote{그러나 어떤, ANSI 이전의
컴파일러에서는 \TT{realloc}이 실패했을 경우, 인자로 전달되었던 메모리
블럭의 보존 여부를 보장하지 않습니다.}
\TT{realloc}을 써서 메모리의 크기를 변경했을 경우에, 다른 포인터가
이 메모리 공간을 가리키고 있었는지 (``alias''라는 용어를 씁니다)
주의해야 합니다: 만약에 \TT{realloc}이 다른 곳에 메모리 블럭을 할당했다면,
다른 포인터들도 값이 올바르게 바뀌어야 합니다. (\TT{malloc}의 실패 여부를
확인하지 않았다는 단점이 있긴 하지만) 누군가가 제공한 코드입니다:
\begin{verbatim}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *p, *p2, *newp;
int tmpoffset;
p = malloc(10);
strcpy(p, "Hello,"); /* p is a string */
p2 = strchr(p, ','); /* p2 points into that string */
tmpoffset = p2 - p;
newp = realloc(p, 20);
if (newp != NULL) {
p = newp; /* p may have moved */
p2 = p + tmpoffset; /* relocate p2 as well */
strcpy(p2, ", world");
}
printf("%s\n", p);
\end{verbatim}
위와 같이 기본값(base)을 기초로 해서 포인터 값을 다시 계산하는 것이
바람직합니다. 다른 방법은--두 값의 차(difference)인 \TT{newp - p}의
값을 기초로, \TT{realloc}을 부르기 전, 후의 베이스 포인터의 값을 쓰는
방식-- 동작한다고 보장할 수 없습니다. 왜냐하면, 포인터 뺄셈은 같은
오브젝트를 가리키는 포인터 사이에서만 의미가 있기 때문입니다.
\seealso{\ql{7.12}, \ql{7.30}}.
\R
\cite{kr2} \S\ B5 \page{252} \\
\cite{ansi} \S\ 4.10.3.4 \\
\cite{c89} \S\ 7.10.3.4 \\
\cite{hs} \S\ 16.3 \Page{387--8}
\end{faq}
\begin{faq}
\Q{7.30}
\TT{realloc} 함수의 첫번째 인자로 널 포인터를 전달하는
것이 안전한가요? 그리고 왜 그런 일을 하는 것이죠?
\A
ANSI C 표준은 그런 식으로 써도 (그리고 \verb+realloc(..., 0)+처럼
써서 메모리를 해제하는 것) 괜찮다고 말합니다. 그러나 대부분의
오래된 구현 방법에서는 이러한 사용법을 제공하지 않습니다.
따라서 표준이기는 하지만 완전히 호환성을 갖춘 방법이 아닙니다.
\TT{realloc}의 첫 인자로 널 포인터를 전달하면 메모리를
증가적으로 동적으로 할당하는 알고리즘을 (self-starting incremental
allocation algorithm) 만들기 쉽습니다.
\R
\cite{c89} \S\ 7.10.3.4 \\
\cite{hs} \S\ 16.3 \page{388}
\end{faq}
\begin{faq}
\Q{7.31}
\TT{calloc()}과 \TT{malloc()}의 차이는 무엇인가요?
\TT{calloc()}이 메모리를 0으로 만드는 것을 믿고 쓸 수 있나요?
그리고, \TT{calloc()}으로 할당한 메모리를 \TT{free()}를 써서
해제할 수 있나요? 아니면 \TT{cfree()}와 같은 것을 사용하나요?
\A
\TT{calloc()}은 다음의 코드를 수행하는 것과 같습니다:
\begin{verbatim}
p = malloc(m * n);
memset(p, 0, m * n);
\end{verbatim}
\noindent 0으로 채운다는 것은 할당한 메모리의 모든 비트를
0으로 채운다는 뜻입니다. 따라서 이 값이 널 포인터가 아닐 수도
있으며(여기에 관한 것은 5 절을 참고하기 바랍니다.)
실수(floating-point)로 0이 아닐 수도 있습니다.
그리고 \TT{calloc()}으로 할당한 메모리를 해제할 때에도 \TT{free()}를
씁니다.
\R
\cite{c89} \S\ 7.10.3부터 7.10.3.2 \\
\cite{hs} \S\ 16.1 \page{386}, \S\ 16.2 \page{386} \\
\cite{pcs} \S\ 11 \Page{141--142}
\end{faq}
\begin{faq}
\Q{7.32}
\TT{alloca()} 함수는 어떤 함수이며, 왜 쓰지 말라고 하죠?
\A
\TT{alloca()}는 이 함수를 호출한 함수가 끝날 때, 자동으로 할당한
메모리를 해제해 주는 함수입니다. 즉, \TT{alloca}로 할당한 메모리는
특정 함수의 ``스택 프레임(stack frame)''이나 문맥(context)에
종속적입니다.
\TT{alloca()} 함수는 이식성있게 만들 수가 없습니다. 그리고
일반적인 스택을 사용하지 않는 컴퓨터에서는 매우 만들기 어려운
함수입니다. 특히 \TT{alloca}로 할당한 메모리를 리턴하는 경우,
심각한 문제가 발생할 수 있습니다. 예를 들면:
\begin{verbatim}
fgets(alloca(100), 100, stdin);
\end{verbatim}
이런 이유에서 \TT{alloca} 함수는 표준 함수가 아니며, 높은 이식성이
요구되는 프로그램에서는 (매우 쓸모 있기는 하지만) 쓸 수 없습니다.
질문 \ql{7.22}를 참고하기 바랍니다.
\R
\cite{rationale} \S\ 4.10.3
\end{faq}
%
% Local Variables:
% coding: utf-8
% fill-column: 78
% End:
%