-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathstruct.tex
807 lines (708 loc) · 35.4 KB
/
struct.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
\chapter{Structures, Unions, and Enumerations} \label{chap:struct}
% chapter 2: struct
Structure, union, enumeration은 여러분에게 새로운 data type을 정의할 수
있게 해준다는 공통점을 가집니다. 먼저, structure나 union은 여러분이
member나 field를 선언하여 새 data type을 정의할 수 있고, enumeration의 경우
상수를 (constant) 선언하여 새 data type을 정의할 수 있습니다.
동시에 여러분은 새로운 data type에 \EM{tag} name을 줄 수 있습니다.
일단 새 type을 정의했다면, 정의와 동시에 또는 나중에 새 type의
instance(변수)를 선언할 수 있습니다.
복잡하게도, 기본 타입과 마찬가지로 user-defined type에도 \TT{typedef}를
써서 새 이름을 줄 수 있습니다. 이렇게 했을 때, 여러분은 \TT{typedef} 이름이
(만약 tag 이름이 존재할 경우) tag 이름과는 전혀 상관없다는 것을 아셔야
합니다.
이 chapter의 질문들은 다음과 같이 정리되어 있습니다:
질문 \ql{2.1}부터 \ql{2.18}은
structure에 대하여, 질문 \ql{2.19}부터 \ql{2.20}까지는 union에 대하여,
질문 \ql{2.25}부터 \ql{2.26}까지는 bitfield에 대해 다룹니다.
\section{Structure Declaration}
% from 2.1
\begin{faq}
\Q{2.1} % 2.1 COMPLETE
다음 두 선언의 차이점이 무엇인가요?
\begin{verbatim}
struct x1 { ... };
typedef struct { ... } x2;
\end{verbatim}
\A
첫번째 선언은 ``구조체 태그(structure tag)''를 선언한 것입니다; 두번째 선언은
``typedef''를 선언한 것입니다. 주 차이점은 첫번째 경우의 타입 이름은
\TT{struct x1}이며, 두번째 경우의 타입 이름은 간단히 \TT{x2}라는
것입니다. 즉 두번째 것이 조금 더 추상화된 타입이라 할 수 있습니다.
--- 즉 사용자들이 \TT{struct}이라는 키워드를 쓰지 않게되어 이 타입이
구조체인지 아닌지 알 필요가 없습니다:
\begin{verbatim}
x2 b;
\end{verbatim}
tag를 써서 정의된 구조체는 다음과 같이 선언해야 합니다.\footnote{C++ 언어에서는
이 두가지 방식이 차이가 없습니다. 몇몇 C++ 컴파일러는 C와 호환을 위해
C 언어처럼 동작합니다. C++에서는 구조체 tag는 자동으로 typedef 이름으로
선언됩니다.}
\begin{verbatim}
struct x1 a;
\end{verbatim}
(두 가지 방식 모두 적용해서 다음과 같이 정의할 수도 있습니다:
\begin{verbatim}
typedef struct x3 { ... } x3;
\end{verbatim}
조금 혼동스럽기는 하지만, tag 이름과 typedef 이름이 중복되는 것은,
서로 다른 namespace에 속하기 때문에, 전혀 상관없습니다.
질문 \ql{1.29}를 보기 바랍니다.)
\end{faq}
\begin{faq}
\Q{2.2} % 2.2 COMPLETE
다음 코드가 왜 동작하지 않을까요?
\begin{verbatim}
struct x { ... };
x thestruct;
\end{verbatim}
\A
C 언어는 C++이 아닙니다. 즉 구조체 태그(tag) 이름에 대해 자동으로
typedef 이름이 만들어지지 않습니다. 실제 구조체는 \TT{struct}
키워드를 써서 정의합니다:
\begin{verbatim}
struct x thestruct;
\end{verbatim}
원한다면, 구조체를 선언할 때, typedef 이름을 같이 선언하고, 이 typedef 이름으로
실제 구조체 변수 또는 상수를 만들 때 쓸 수 있습니다:
\begin{verbatim}
typedef struct { ... } tx;
tx thestruct;
\end{verbatim}
\seealso{\ql{2.1}}
\end{faq}
\begin{faq}
\Q{2.3} % 2.3 COMPLETE
구조체가 자신에 대한 포인터를 포함할 수 있나요?
\A
당연히 포함할 수 있습니다. typedef와 함께 쓸 때 조금 문제가 될 수 있습니다.
이 점에 대해서는 질문 \ql{1.14}, \ql{1.15}를 보기 바랍니다.
\end{faq}
\begin{faq}
\Q{2.4} % 2.4 COMPLETE
C 언어에서 추상화된 데이터 타입을 구현하는 가장 좋은 방법을
알려주세요.
\A
한 가지 방법은 사용자들이 구조체 포인터를 쓰게 하는 것입니다 ---
이 때, typedef 이름을 쓰는 것이 좋습니다. 이 포인터는 어떤 구조체를
가리키는 것이며, 이 구조체의 세부 사항은 사용자에게 알려줄 필요가
없습니다.
다시 말해서, client는 이 구조체가 어떠한 멤버를 포함하고 있는 지, 전혀
알 필요없이, 이 구조체 포인터를 사용합니다. (함수를 부를때,
이 포인터를 넘겨주며, 이 구조체 포인터를 리턴하는 등)
구조체에 대한 자세한 사항을 몰라도 될 경우에 --- \verb+->+ 연산자나
\TT{sizeof} 연산자가 쓰이지 않는다면 --- C 언어로 완전하지 않은,
incomplete type에 대한 포인터를 쓸 수 있습니다. 단지, 이 구조체를
실제 다루는 함수가 들어있는 파일안에서 완전한 정의를 제공하면 됩니다.
\seealso{\ql{11.5}}
\T
표준 입출력 함수들이 \TT{FILE *} 타입의 인자를
받는 것을 생각하면 쉽습니다.
\end{faq}
\begin{faq}
\Q{2.5} % 2.5 COMPLETE
아래와 같이 선언했더니, 이상한 경고 메시지(``struct x introduced in
prototype scope'' 또는 ``struct x declared inside parameter list'')가
발생합니다:
\begin{verbatim}
extern f(struct x *p);
\end{verbatim}
\A
질문 \ql{11.5}를 보기 바랍니다.
\end{faq}
\begin{faq}
\Q{2.6} % 2.6 COMPLETE
구조체를 다음과 같이 정의하는 코드를 봤습니다:
\begin{verbatim}
struct name {
int namelen;
char namestr[1];
};
\end{verbatim}
그리고나서 \TT{namestr}에 공간을 할당하고 (allocation),
배열 \TT{namestr}이 여러 element를 가진 것처럼 쓰는 것을 봤습니다. 이게
안전한 방법인가요?
\A
합법적인지(legal), 이식성이 뛰어난 지는 확실하지 않으나, 매우 인기있는
방법입니다.
보통 다음과 같은 코드를 써서 위 구조체를 사용합니다:
\begin{verbatim}
#include <stdlib.h>
#include <string.h>
struct name *makename(char *newname)
{
struct name *ret =
malloc(sizeof(struct name) - 1 +
strlen(newname) + 1);
/* -1 for initial [1]; +1 for \0 */
if (ret != NULL) {
ret->namelen = strlen(newname);
strcpy(ret->namestr, newname);
}
return ret;
}
\end{verbatim}
이 함수는 \TT{name} 구조체가, 그 멤버인 \TT{namestr}이 주어진
문자열을 충분히
포함할 수 있도록 (단순히 크기 1인 배열이 아닌) 공간을 할당합니다.
이것은 인기있는 테크닉 중의 하나이지만 Dennis Ritchie씨는
이 방법을 ``unwarranted chumminess with the C implementation''이라고
부릅니다. 이 방법은 공식적인 C 표준에 정확히 부합하지는 않지만,
(이것이 표준에 부합하는지 아닌지에 대한 열렬한 논쟁이 있지만,
이 책의 범위를 벗어나므로 생략합니다.)
현존하는 거의 모든 컴파일러에서 동작합니다. (배열의 경계를
검사해주는 컴파일러에서는 경고를 출력할 수도 있습니다.)
% "unwarranted chumminess with the C implementation." An official
% interpretation has deemed that it is not strictly conforming
% with the C Standard, although it does seem to work under all
% known implementations. (Compilers which check array bounds
% carefully might issue warnings.)
또 한가지 방법으로는 가변적인 요소를 매우 작게 잡든 대신 아주 크게
잡는 것입니다; 위의 예에 적용하자면:
\begin{verbatim}
#include <stdlib.h>
#include <string.h>
#define MAX 100
struct name {
int namelen;
char namestr[MAX];
};
struct name *makename(char *newname)
{
struct name *ret =
malloc(sizeof(struct name) - MAX +
strlen(newname) + 1);
/* -1 for initial [1]; +1 for \0 */
if (ret != NULL) {
ret->namelen = strlen(newname);
strcpy(ret->namestr, newname);
}
return ret;
}
\end{verbatim}
\noindent 이 때 \TT{MAX}는 예상되는 저장될 문자열보다 크게
잡습니다.
그러나 이 방법도 표준에 정확히 부합하는 방법도 아닙니다.
게다가 이런 구조체를 쓸 때에는 매우 주의를 기울여야 합니다.
왜냐하면 이런 경우에는 컴파일러보다 프로그래머가 그 크기에 대해
더욱 잘 알고 있기 때문에 (특히, 이런 경우 pointer로만 작업을
할 수 있습니다.) 컴파일러가 알려주는 정보(경고나 에러)가
의미없게 됩니다.
가장 올바른 방법은, 다음과 같이, 배열 대신에 문자 포인터를 쓰는
것입니다:
\begin{verbatim}
#include <stdlib.h>
#include <string.h>
struct name {
int namelen;
char *namep;
};
struct name *makename(char *newname)
{
struct name *ret = malloc(sizeof(struct name));
if (ret != NULL) {
ret->namelen = strlen(newname);
ret->namep = malloc(ret->namelen + 1);
if (ret->namep == NULL) {
free(ret);
return NULL;
}
strcpy(ret->namestr, newname);
}
return ret;
}
\end{verbatim}
위와 같이 하면, 문자열의 길이와 실제 문자열을 하나의 메모리
블럭에 저장한다는 ``편리함''이 사라집니다. 또한 위와 같이 할당한
메모리를 돌려주기 위해서는
\TT{free}를 두 번 불러야 합니다; 질문 \ql{7.23}을 참고하기 바랍니다.
만약에, 위와 같이, 저장할 데이터 타입이 문자(character)라면,
\TT{malloc}을 두 번 부르는 것을 다시 한 번으로 줄여서, 연속성을
보장할 수 있는 방법이 있습니다.
(따라서 \TT{free}를 한 번만 불러도 됩니다):
\begin{verbatim}
struct name *makename(char *newname)
{
char *buf = malloc(sizeof(struct name) +
strlen(newname) + 1);
struct name *ret = (struct name *)buf;
ret->namelen = strlen(newname);
ret->namep = buf + sizeof(struct name);
strcpy(ret->namep, newname);
return ret;
}
\end{verbatim}
그러나, 위와 같이, \TT{malloc}을 한 번 불러서, 두번째 영역까지
할당하는 것은, 두 번째 영역이 \TT{char} 배열로 취급될 경우에만
이식성이 있습니다. 다른, 더 큰 데이터 타입을 쓴다면, alignment
(질문 \ql{2.12}, \ql{16.7} 참고) 문제가
발생할 가능성이 높습니다.
\cite{c9x}에서는 ``flexible array member''라는 개념을 소개하고 있고,
이는 배열이 구조체의 마지막 멤버로써 쓰일 때에는 배열의 크기 지정을
생략할 수 있도록 해 줍니다.
\R
\cite{rationale} \S\ 3.5.4.2 \\
\cite{c9x} \S\ 6.5.2.1
\T
C99 표준에 소개된, ``flexible array member''를 쓰면, 이 문제를
깔끔하게 해결할 수 있습니다. 반드시
질문 \ql{11.G}를 읽기 바랍니다.
\end{faq}
\section{Structure Operations}
% from 2.7
\begin{faq}
\Q{2.7} % 2.7 COMPLETE
Structure가 변수에 대입되고 함수 인자로 전달될 수 있다고 들었지만,
\cite{kr1}에서는 그렇지 않다고 하는군요.
\A
\cite{kr1}에서도, 앞으로 만들어질 컴파일러에서는 이러한 제한들이
없을 것이라고 씌여 있습니다. 그리고 실제로도, Ritchie가 만든
컴파일러에서도 (\cite{kr1} 출판 이후) 구조체를 대입하고, 함수
인자로 전달하고, 구조체를 리턴하는 모든 연산들이 허용되었습니다.
아주 오래된 몇몇 C 컴파일러의 경우,
이 제한이 남아있지만 대부분의 컴파일러들은 구조체 연산을 지원합니다.
그리고 이 연산들은 이제 ANSI C 표준의 일부가 되었습니다. 따라서 전혀
거리낌없이 쓰셔도 좋습니다.\footnote{그러나, 큰 구조체를 함수의 인자로
전달하는 것은 효율성이 매우 떨어집니다. (질문 \ql{2.9} 참고)
따라서 이 구조체에 대한 포인터를 쓰는 것이 (물론 pass-by-value의
의미가 필요없다는 전제 아래에서) 훨씬 바람직합니다.}
(구조체가 복사, 전달, 리턴되는 경우, 복사 작업은 통채로(monolithically)
이루어지기 때문에 구조체 안의 포인터 멤버 필드가 가리키는 데이터는
복사되지 않는다는 것에 주의하시기 바랍니다.)
질문 \ql{14.11}의 코드를 참고하기 바랍니다.
\R
\cite{kr1} \S\ 6.2 \page{121} \\
\cite{kr2} \S\ 6.2 \page{129} \\
\cite{ansi} \S\ 3.1.2.5, \S\ 3.2.2.1, \S\ 3.3.16 \\
\cite{c89} \S\ 6.1.2.5, \S\ 6.2.2.1, \S\ 6.3.16 \\
\cite{hs} \S\ 5.6.2 \page{133}
\end{faq}
\begin{faq}
\Q{2.8} % 2.8 COMPLETE
왜 \verb+==+나 \verb+!=+를 써서 구조체를 비교할 수 없나요?
\A
C 언어의 저수준성을 고려해서, 구조체 비교를 구현하기 위한 좋은 방법은,
컴파일러 입장에서, 존재하지 않습니다. 간단히 바이트 단위로
비교하는 것은, 구조체 필드 사이에 있을지도 모르는 ``hole''들을 생각할 때 좋지
않습니다. (이러한 `hole'은 각 필드가 주어진 정렬(alignment) 방법에
맞도록 위치시키기 위해 필요합니다. 질문 \ql{2.12}를 참고하시기 바랍니다.)
또한 구조체안에 많은 필드가 있을 때, 모든 것을 필드 단위로 비교하는 것은,
반복되는 코드가 많아서 적당하지 않을 수도 있습니다.
또 컴파일러가 자동으로 만들어 낸 비교 코드가 모든 경우에, 구조체 포인터 멤버들을
제대로 비교할 수 있는 것도 어렵습니다; 예를 들어, 대부분의 경우에, \TT{char *}
타입의 필드는 \verb+==+로 비교하는 것보다 \TT{strcmp}로 비교하는 것이 더
바람직합니다. (질문 \ql{8.2} 참고)
여러분이 두 개의 구조체를 비교하길 원한다면, 필드 단위로 구조체를
비교하는 함수를 직접 만들어야 합니다.
\R
\cite{kr2} \S\ 6.2 \page{129} \\
\cite{ansi} 4.11.4.1 footnote 136 \\
\cite{rationale} \S\ 3.3.9 \\
\cite{hs} \S\ 5.6.2 \page{133}
\end{faq}
\begin{faq}
\Q{2.9} % 2.9 COMPLETE
구조체를 전달하거나, 리턴할 때 쓰면, 실제로 어떻게 구현되는 것인가요?
\A
함수의 인자로 구조체가 전달되면, 구조체의 모든 값이 스택에 저장됩니다. (대부분의 프로그래머가
이 복사에 해당하는 overhead를 줄이기 위해서 구조체를 직접 전달하지 않고, 대신 구조체의
포인터를 씁니다.) 어떤 컴파일러들은 자동으로 구조체를 가리키는 포인터를 전달하기도 하지만,
때때로 pass-by-value 개념을 위해, 따로 복사본을 만들어야 할 필요가 발생합니다.
함수의 리턴 타입으로 구조체가 쓰이면, 대부분은 따로 컴파일러가 마련한, 보이지 않은 곳,
일반적으로 함수의 인자 형태로 저장됩니다. 어떤 오래된 컴파일러는 구조체를 리턴할 때 쓸
목적으로 특별한, static 공간을 마련합니다. 이런 경우, 구조체를 리턴하는 함수가
다시 재진입(reentrant)할 수 없기 때문에, ANSI C에 부합하지 않습니다.
\R
\cite{ansi} \S\ 2.2.3 \\
\cite{c89} \S\ 5.2.3
\end{faq}
\begin{faq}
\Q{2.10} % 2.10 COMPLETE
구조체 인자를 받아들이는 함수에 상수값을 (constant value) 전달할 수 있나요?
\A
이 글을 쓸 당시 C 언어에는 이름 없는(anonymous) 구조체를
만들 방법이 없었습니다. 따라서 임시 구조체 변수를 만들거나 구조체를
리턴하는 함수를 써야 합니다. 질문 \ql{14.11}을 보기 바랍니다.
(GNU C 컴파일러는 구조체 상수를, 추가 기능으로 제공하며, 이후 C 표준에
채택될 가능성이 높습니다.) 질문 \ql{4.10}을 참고하기 바랍니다.
드디어, \cite{c9x} 표준은 ``compound literal''이라는 개념을 소개합니다;
`compound literal'의 한가지 형태는 구조체 상수를 쓸 수 있게 해
줍니다. 예를 들어, \TT{struct point} 타입의 인자를 받는
\TT{plotpoint()}에 상수 구조체를 전달하려면 다음과 같이 할 수 있습니다:
\begin{verbatim}
plotpoint((struct point){1, 2});
\end{verbatim}
(또다른 \cite{c9x} 표준인) ``designated initializer''라는 개념을 함께 쓰면,
각각의 멤버 이름을 지정할 수도 있습니다:
\begin{verbatim}
plotpoint((struct point){.x = 1, .y = 2});
\end{verbatim}
\R
\cite{c9x} \S\ 6.3.2.5, \S\ 6.5.8
\end{faq}
\begin{faq}
\Q{2.11} % 2.11 COMPLETE
구조체를 화일에서 읽거나 쓰는 방법은?
\A
구조체를 화일에 쓸 경우는 대개 \TT{fwrite()} 함수를 씁니다:
\begin{verbatim}
fwrite(&somestruct, sizeof somestruct, 1, fp);
\end{verbatim}
\noindent
그리고, 이렇게 쓴 데이터는 \TT{fread()}를 써서 읽을 수 있습니다.
그러면 \TT{fwrite}가 구조체를 가리키고 있는 포인터를 써서,
주어진 구조체가 저장되어 있는 메모리의 내용을 파일에 기록합니다.
(\TT{fread}의 경우에는 파일에서 읽어옵니다.) 이 때 \TT{sizeof} 연산자는
복사할 byte 수를 지정합니다.
ANSI 호환의 컴파일러를 쓰고, 함수 선언이 되어 있는 헤더 파일을
(대개 \verb+<stdio.h>+) 포함했으면 위와 같이 쓰는 것이 좋습니다.
만약 ANSI 이전의 컴파일러를 쓰고 있다면, 첫번째 인자를 다음과 같이
캐스팅해주어야 합니다:
\begin{verbatim}
fwrite((char *)&somestruct, sizeof somestruct, 1, fp);
\end{verbatim}
여기서 중요한 것은, \TT{fwrite}가 구조체에 대한 포인터가 아니라,
바이트를 가리키는 포인터를 받는다는 것입니다.
위와 같이 쓰여진 데이터 화일은 (특히 구조체가 포인터나 실수(floating point)를
포함하고 있을 때) 이식성이 없습니다. (질문 \ql{2.12}와 \ql{20.5}를
참고하기 바랍니다). 구조체가 저장되는 메모리 이미지는 컴퓨터와 컴파일러에 매우
의존적입니다. 각각 다른 컴파일러는 각각 다른 크기의 padding을 사용할 수 있으며,
바이트 크기와 순서(endian)가 다를 수 있습니다.
한 시스템에서 구조체를 어떤 파일에 쓰고(write), 다른 시스템에서 이 파일의 내용을
올바르게 읽는 것이 중요하다면, 질문 \ql{2.12}와 \ql{20.5}를 참고하기 바랍니다.
만약 구조체가 포인터를 (\TT{char *} 타입의 문자열이나 다른 구조체를 가리키고 있는
포인터) 포함하고 있었다면, 단지
포인터 값만 기록되기 때문에, 화일에서 이 데이터를 읽을 경우, 의미없는
값이 됩니다. 또한 데이터 파일의 이식성을 높이기 위해서는
\TT{fopen()}을 호출할 때, ``\TT{b}'' flag을 써야 합니다;
질문 \ql{12.38}을 참고하시기 바랍니다.
좀 더 이식성이 높은 방법은, 구조체를 필드 단위로 읽고 쓰는 함수를
만들어 쓰는 것입니다.
\R
\cite{hs} \S\ 15.13 \page{381}
\end{faq}
\section{Structure Padding}
% from 2.12
\begin{faq}
\Q{2.12} % 2.12 COMPLETE
제 컴파일러는 구조체 안에 `hole'을 만들어 넣어서 공간을
낭비하고 외부 데이터 화일에 ``binary'' I/O를 불가능하게 합니다.
이 `padding' 기능을 끄거나 구조체 필드의 alignment를 조정할 수
있을까요?
\A
대부분의 컴퓨터는 데이터들이 적절하게 align되어 있을 때, 메모리를 가장 효과적으로
읽고 쓸 수 있습니다. 예를 들어 byte-address machine에서 \TT{short int}나
크기가 2인 데이터는 짝수로 된 주소에 위치해 있는 것이 가장 효과적이며,
\TT{long int}나 크기가 4인 데이터는 4의 배수로 된 주소에 위치하는 것이
좋습니다. 어떤 머신에서는, 위와 달리 제대로 위치해 있지 않은 데이터는 아예 읽고
쓸 수 없을 수 있습니다.
다음과 같은 구조체가 있다고 가정해 봅시다:
\begin{verbatim}
struct {
char c;
int i;
};
\end{verbatim}
대부분의 컴파일러에서 위와 같은 구조체를 만들 때, \TT{char}와 \TT{int} 사이에
어떤 `hole'을 만들어서 \TT{int} 값이 제대로 align될 수 있도록 해 줍니다.
(이렇게 두번째 필드를 첫번째 필드를 기준으로 하여, 증가하는 방식의 정렬을
(incremental alignment) 쓰는 것은, 이 구조체 자체가 올바르게 정렬되어 있다는
것을 가정한 것입니다. 결국 컴파일러는 \TT{malloc}이 하는 것처럼, 구조체를 할당할 때
올바른 align을 보장해 주어야 합니다.)
아마도 컴파일러에서 이런 (`padding'이나 `hole'을 어떻게 쓰는지) 제어를 할 수 있는
방법을 제공할 것입니다.
(\verb+#pragma+를 써서 할 수 있습니다; 질문 \ql{11.20}을 참고하기
바랍니다), 그러나 이런 제어를 위한 표준 방법은 없다는 것을 아셔야
합니다.
이러한 padding으로 발생하는, 낭비되는 공간이 염려된다면, 구조체의 멤버를, 큰 크기에서
작은 크기 순으로 정의하면, 낭비되는 공간을 줄일 수 있습니다. bit field를 사용하면
더욱 많은 공간을 절약할 수 있으나, bit field 나름대로의 단점도 존재합니다. (질문
\ql{2.26}을 참고하기 바랍니다.)
\seealso{\ql{16.7}, \ql{20.5}}
\R
\cite{kr2} \S\ 6.4 \page{138} \\
\cite{hs} \S\ 5.6.4 \page{135}
\end{faq}
\begin{faq}
\Q{2.13} % 2.13 COMPLETE
구조체 타입에 \TT{sizeof} 연산자를 썼더니, 제가 예상한 것보다
훨씬 큰 값을 리턴합니다. 왜 그러죠?
\A
구조체는 필요한 경우 이러한 `padding' 공간을 포함할 수 있습니다.
이는 구조체가 배열로 만들어질 때, 정렬(alignment) 속성이 보존되도록
하기 위한 것입니다. 또 배열로 쓰이지 않을 경우에도 이러한 여분의
`padding'이 남아 있을 수 있습니다. 이는 \TT{sizeof}가 일관된
크기를 리턴하게 하기 위한 것입니다. 질문 \ql{2.12}를 참고하기 바랍니다.
\R
\cite{hs} \S\ 5.6.7 \Page{139--40}
\end{faq}
\section{Accessing Members}
% from 2.14
\begin{faq}
\Q{2.14} % 2.14 COMPLETE
구조체 안에서 각각의 필드에 대한 byte offset을 얻을 수 있나요?
\A
ANSI C는 \TT{offsetof()} 매크로를 정의합니다. \verb+<stddef.h>+를
보시기 바랍니다. 만약에 이 매크로가 없다면 다음과 같이 만들 수
있습니다:
\begin{verbatim}
#define offsetof(type, mem) ((size_t) \
((char *)&((type *)0)->mem - (char *)(type *)0))
\end{verbatim}
이 방법은 100\% 이식성이 뛰어난 것이 아닙니다. 어떤 컴파일러에서는
이 방법을 쓸 수 없을 수 있습니다.
(복잡하기 때문에 조금 더 설명하면, 잘 캐스팅된 널 포인터를 빼는 것은
널 포인터가 내부적으로, 0이 아닌 다른 값이더라도 offset 값을 얻을 수 있게
보장해 줍니다. \TT{(char *)}로 캐스팅하는 것은 offset이 byte offset 단위로
결과를 얻을 수 있게 하기 위한 것입니다.
호환성이 없는 부분이 하나 있는데, 주소 계산을 할 때, \TT{type} 오브젝트가
주소 0번지에 있다고 속이는 부분에 있습니다만, 실제로 참조되지 않기 때문에
access violation이 일어날 가능성은 없습니다.)
쓰는 방법을 알기 위해, 질문 \ql{2.15}를 참고하기 바랍니다.
\R
\cite{ansi} \S\ 4.1.5 \\
\cite{c89} \S\ 7.1.6 \\
\cite{rationale} \S\ 3.5.4.2 \\
\cite{hs} \S\ 11.1 \Page{292--3}
\end{faq}
\begin{faq}
\Q{2.15} % 2.15 COMPLETE
실행 시간(run-time)에 구조체 필드를 이름으로 access할 수 있습니까?
\A
각각의 필드 이름과 offset을 \TT{offsetof()} 매크로를 써서 테이블에
저장해 두면 됩니다. \TT{struct a}에서 필드 `\TT{b}'의 오프셋은 다음과
같이 얻을 수 있습니다:
\begin{verbatim}
offsetb = offsetof(struct a, b)
\end{verbatim}
만약 이 구조체 변수를 가리키는 포인터 \TT{structp}가 있고,
필드 `\TT{b}'가 \TT{int}일때, 위에서 계산한 \TT{offsetb}를
쓰면 \TT{b}의 값을 다음과 같이 설정할 수 있습니다:
\begin{verbatim}
*(int *)((char *)structp + offsetb) = value;
\end{verbatim}
\end{faq}
\begin{faq}
\Q{2.16} % 2.16 COMPLETE
C 언어에, Pascal의 \TT{with} 문장과 같은 기능이 있습니까?
\A
질문 \ql{20.23}을 보기 바랍니다.
\end{faq}
\section{Miscellaneous Structure Questions}
% from 2.17
\begin{faq}
\Q{2.17} % 2.17
배열의 이름이, 배열의 첫번째 요소를 가리키는 포인터처럼 동작한다면,
왜 구조체 이름은 비슷한 방식으로 동작하지 않을까요?
\A
The rule (see question \ql{6.3}) that causes array references to
``decay'' into pointers is a special case that applies only to
arrays and that reflects their ``second-class'' status in C.
(An analogous rule applies to functions.) Structures, however,
are first-class objects: When you mention a structure, you get the
entire structure.
\end{faq}
\begin{faq}
\Q{2.18} % 2.18
이 프로그램은 정상적으로 동작하지만 프로그램이 끝났을 때,
\TT{core} 파일을 만들어 냅니다. 왜 그런가요?
\begin{verbatim}
struct list {
char *item;
struct list *next;
}
/* Here is the main program. */
main(argc, argv)
{ ... }
\end{verbatim}
\A
구조체를 정의할 때 세미콜론(`\TT{;}')을 빠뜨렸기 때문에 \TT{main()}이
구조체를 리턴하는 함수로 정의되어 버렸습니다. (중간에 들어간
주석(comment) 때문에 이 버그를 찾아내기가 더욱 힘들 것입니다)
대개 구조체를 리턴하는 함수들은 숨겨진 리턴 포인터(hidden return pointer)를
추가하는 식으로 구현되기 때문에, \TT{main()}이 세개의 인자를 받는 것처럼
만들어집니다. 원래
C start-up code는 \TT{main()}이 두 개의 인자를 받는 것으로 짜여져
있으므로, 이 경우 정상적으로 동작할 수 없습니다. 질문 \ql{10.9}와 \ql{16.4}를
참고하기 바랍니다.
\R
\cite{ctp} \S\ 2.3 \Page{21--2}
\end{faq}
\section{Unions}
% from 2.19
\begin{faq}
\Q{2.19} % 2.19 COMPLETE
structure와 union은 어떻게 서로 다른가요?
\A
union은 모든 필드가 서로 겹쳐서 존재한다는 것을 제외하고는, structure와 같습니다;
따라서 union에서는 한 번에 하나의 필드만을 쓸 수 있습니다. (물론 하나의 필드에
쓰고(write), 다른 필드에서 읽어서, 어떤 타입의 비트 패턴을 조사하거나 다른 식으로
해석하기 위해서 쓸 수도 있지만, 이는 상당히 machine dependent한 것입니다.)
union의 크기는, 각각의 멤버들 중 가장 큰 멤버의 사이즈가 됩니다. 반면에 structure의
크기는 각각의 멤버들의 크기를 다 더한 값입니다. (두 가지 경우 모두, 적절한 padding 값이
더해질 수 있습니다; 질문 \ql{2.12}와 \ql{2.13}을 참고하기 바랍니다.)
\R
\cite{ansi} \S\ 3.5.2.1 \\
\cite{c89} \S\ 6.5.2.1 \\
\cite{hs} \S\ 5.7 \Page{140--5} esp.\ \S\ 5.7.4
\end{faq}
\begin{faq}
\Q{2.20} % 2.20
\TT{union}을 초기화(initialization)할 수 있습니까?
\A
현재 C 표준은 \TT{union} 안에, 이름이 있는 첫번째 멤버의 초기화만
인정합니다. (ANSI 이전의 컴파일러에서는 union을 초기화할 수 있는 방법이 전혀 없습니다.)
union을 초기화하기 위해, 여러 제안이 있었지만 아직 채택된 것은 없습니다.
(GNU C 컴파일러는 어떤 멤버라도 초기화할 수 있는 확장 기능을 제공하며, 곧 이 기능이
표준으로 채택될 가능성이 높습니다.)
If you're really desperate, you can sometimes define several
variant copies of a union, with the members in different orders,
so that you can declare and initialize the one having the
appropriate first member. (These variants are guaranteed to be
implemented compatibly, so it's okay to ``pun'' them by initializing
one and then using the other.)
\cite{c9x}는 ``designated initializer''를 소개하고 있으며, 어떠한
멤버의 초기값도 쓸 수 있도록 하고 있습니다.
\T
\cite{c9x}에 소개된 예는 다음과 같습니다:
\begin{verbatim}
union { /* ... */ } u = { .any_member = 42 };
\end{verbatim}
즉, 위의 예는 union \TT{u}의 멤버인 ``\verb+any_member+''를
42로 초기화하고 있습니다.
\R
\cite{kr2} \S\ 6.8 \Page{148--9} \\
\cite{c89} \S\ 6.5.7 \\
\cite{c9x} \S\ 6.7.8 \\
\cite{hs} \S\ 4.6.7 \page{100}
\end{faq}
\begin{faq}
\Q{2.21} % 2.21
union의 어떤 필드가 현재 쓰여지고 있는지 알 수 있는 방법이 있을까요?
\A
없습니다. 여러분이 직접 어떤 필드가 쓰여지고 있는지 기록해 두어야 합니다.
\begin{verbatim}
struct taggedunion {
enum { UNKNOWN, INT, LONG, DOUBLE, POINTER } code;
union {
int i;
long l;
double d;
void *p;
} u;
};
\end{verbatim}
위와 같이 만들고, union에 값을 쓸(write) 때마다 \TT{code}를 올바른 값으로
설정하면 됩니다; 컴파일러가 자동으로 이런 것을 해 주지는 않습니다.
(C 언어에서 union은 Pascal의 variant record와는 다릅니다.)
\R
\cite{hs} \S\ 5.7.3 \Page{143}
\end{faq}
\section{Enumerations}
% from 2.22
\begin{faq}
\Q{2.22} % 2.22
enumeration(열거형)을 쓰는 것과 \verb+#define+으로 정의한
매크로를 쓰는 것과 차이가 있습니까?
\A
현재 이 둘에는 약간의 차이가 있습니다. C 표준은 `enumeration이
에러없이 정수형 타입으로 쓰일 수 있다'고 정의할 수 있습니다.
(달리 표현하면, 이렇게 명백하게 캐스트를 하지 않고 섞어쓸 수 없었다면
현명하게 enumeration을 썼을 때에만 프로그래밍 에러를 잡아 줄 수
있었을 겁니다. If such intermixing were disallowed without explicit
casts, judicious use of enumerations could catch certain programming
errors.) % TODO: 이게 몬 소리여? -_-;;;;
enumeration을 썼을 때에 좋은 점은, 수치 값이 자동적으로 대입되기 때문에,
디버거(debugger)가 열거형 변수를 검사할 때 심볼(symbol) 값으로
보여줄 수 있다는 점입니다.
(enumeration과 정수형을 섞어쓰는 것이 오류는 아니지만, 좋은 스타일이 아니기
때문에 어떤 컴파일러는 가벼운 경고를 출력하기도 합니다.)
enumeration을 쓸 때의 단점은 이러한 사소한 경고를 프로그래머가 처리해 줘야
한다는 것입니다; 어떤 프로그래머들은 enumeration 변수의 크기를 제어할 수
없다는 것에 불평하기도 합니다.
\R
\cite{kr2} \S\ 2.3 \page{39}, \S\ A4.2 \page{196} \\
\cite{ansi} \S\ 3.1.2.5, \S\ 3.5.2, \S\ 3.5.2.2, Appendix E \\
\cite{c89} \S\ 6.1.2.5, \S\ 6.5.2, \S\ 6.5.2.2, Annex F \\
\cite{hs} \S\ 5.5 \Page{127--9}, \S\ 5.11.2 \page{153}
\T
최신의 GCC에 적절한 디버깅 옵션을 주면, 디버거 GDB에서, 매크로도
값이 아닌, 이름(symbol 값)으로 보여줍니다. 따라서 이 경우, enum이
가지는 장점 하나는 없어진다고 말할 수 있습니다.
\end{faq}
\begin{faq}
\Q{2.23} % 2.23
enumeration은 이식성이 있나요?
\A
Enumeration은 그리 오래지 않아 C 언어에 포함되었습니다. (K\&R1에는
없었습니다.) 그렇지만 확실하게 C 언어의 일부가 되었으며, 따라서 모든 현대
컴파일러들은 이를 지원합니다. They're quite portable, although historical
uncertainty about their precise definition led to their specification
in the Standard being rather weak. (질문 \ql{2.22}를 참고하기 바랍니다.)
\end{faq}
\begin{faq}
\Q{2.24} % 2.24
열거형 값을 심볼로 출력할 수 있는 간단한 방법이 있을까요?
\A
없습니다. 열거형 값을 문자열로 매핑(mapping)시켜주는
함수를 직접 만들어야 합니다. (디버깅 목적으로, 성능 좋은 디버거들은
자동으로 열거형 값을 심볼로 보여주기도 합니다.)
\end{faq}
\section{Bitfields}
% from 2.25
\begin{faq}
\Q{2.25} % 2.25
아래와 같이 구조체 선언에 콜론(:)을 쓰는게 뭐죠?
\begin{verbatim}
struct record {
char *name;
int refcount : 4;
unsigned dirty : 1;
};
\end{verbatim}
\A
비트 필드(bitfield)라고 합니다; 콜론(:) 다음에 오는 것은, 콜론 앞 필드의
정확한 크기를 비트 단위로 나타냅니다. (C 언어를 자세히 다루는 여러 책에서 잘 다루고
있습니다.) 여러 비트 단위의 flag이나 작은 값들을 저장하는 데 bitfield를 사용하면
공간을 절약할 수 있습니다. 어떤, 알려진 저장 방식에 정확히 일치하는 구조체를 만드는데
쓰기도 합니다. (Their success at the latter task is mitigated by the
fact that bitfields are assigned left to right on some machines
and right to left on others.)
콜론을 써서 비트 필드의 크기를 정하는 것은 구조체나 union에서만 쓸 수 있습니다.
다른 변수 타입의 크기를 직접 정하려고 쓸 수 없습니다. (질문 \ql{1.2}와 \ql{1.3} 참고)
\R
\cite{kr1} \S\ 6.7 \Page{136--8} \\
\cite{kr2} \S\ 6.9 \Page{149--50} \\
\cite{ansi} \S\ 3.5.2.1 \\
\cite{c89} \S\ 6.5.2.1 \\
\cite{hs} \S\ 5.6.5 \Page{136--8}
\end{faq}
\begin{faq}
\Q{2.26} % 2.26
Why do people use explicit masks and bit-twiddling code so much
instead of declaring bitfields?
\A
Bitfields are thought to be nonportable, although they are no less
portable than other parts of the language. You don't know how large
they can be, but that's equally true for values of type \TT{int}.
You don't know by default whether they're signed, but that's equally
true of type \TT{char}. You don't know whether they're laid out from
left to right or right to left in memory, but that's equally true of
the bytes of \EM{all} types and matters only if you're trying to
conform to externally imposed storage layout. (Doing so is always
nonportable; see also question \ql{2.12} and \ql{20.5}.)
Bitfields are inconvenient when you also want to be able to manipulate
some collection of bits as a whole (perhaps to copy a set of flags).
You can't have arrays of bitfields; see also question \ql{20.8}.
Many programmers suspect that the compiler won't generate good code
for bitfields; historically, this was sometimes true.
Straightforward code using bitfields is certainly clearer than the
equivalent explicit masking instructions; it's too bad that bitfields
can't be used more often.
\end{faq}
%
% Local Variables:
% coding: utf-8
% fill-column: 78
% End:
%