Program tend to last longer than their authors ever dreamed, even when written only for the author's own use. Thus it is not enough to do what works now and ignore the future. Yet we have just seen that trying to be as portable as possible can be expensive by denying us today's benefits in order to live with yesterday's tools.
The best we can do about decisions like these is to admit that they are decisions and not let them be made by accident.
너무 명언이라 가져옴.
In fact, all ANSI C guarantees is that the implementation will distinguish external names that differ in the first six characters. For the purpose of this definition, upper-case letters do not differ from the corresponding lower-case letters.
char *Malloc(unsigned n)
{
char *p, *malloc(unsigned);
p = malloc(n);
if (p == NULL)
panic("out of memory");
return p;
}
This function will be recursed without exiting.
- The three sizes of integers are nondecreasing. That is, a short integer can contain only values that will also fit in a plain integer and a plain integer can contain only values that will also fit in a long integer.
- An ordinary integer is large enough to contain any array subscription.
- The size of a character is natural for the particular hardware.
The most important thing is that one cannot count on having any particular precision available.
지금은 specified-width integer types
(C99 이래로) 가 생겼기 때문에 이들을 사용하면 portable
한 코드를 작성할 수 있음.
Most modern computers support 8-bit characters, so most modern C compilers implement characters as 8-bit integers. However, not all compilers interpret those 8-bit quantities the same way.
If you care whether a character value with the high-order bit on is treated as a negative number, you should probably declare it as
unsigned char
.
- In a right shift, are vacated bits filled with zeroes or copies of the sign bit?
-> The answer isimplementation-defined behavior
- What values are permitted for the shift count?
-> if the item being shifted isn
bits long, then the shift count must be greater than or equal to zero and strictly less thann
.
2번 규칙의 경우 Syntax error 로 넘어가지 않고 Undefined Behavior 로 취급된다:
The result value is also undefined if the value of the right operand is greater than or equal to the width (in bits) of the value of the converted left operand.
C: A Reference Manual 5/e, 232pg.
Strictly speaking, this is not a portability problem: the effect of misusing a null pointer is undefined in all C programs.
그냥 접근하지 말라고.
C99 로 넘어오면서 규칙이 좀 바뀌었지만 책의 내용을 간단하게 요약하자면...
q = a / b;
r = a % b;
q*b + r == a
, because this is the relation that defines the remainder.a
, we want that to change the sign of q
, but not the magnitude.b>0
, we want to ensure that r>=0
and r<b
. For instance, if the remainder is being used as an index to a hash table, it is important to be able to know that it will always be a valid index.C89 (혹은 그 이전) 에선 , 그리고 일때 이라는 조건 아래에서 규칙 1
만 지켜진다. 이게 참 골때리는데 a
가 양수, b
가 음수인 상황에서 q
가 음수라면, q
는 그 몫이 1
더 작아질 수 있다. 무슨 말이냐 하면
q = 5 / -3;
r = 5 % -3;
을 계산했을 때 q = -2
이 되고 r = -1
이 될 수도 있다. -2 * (-3) + (-1) == 5
가 성립하기만 하면 된다.
이 골때리는 규칙은 C99
에서 truncated toward zero
라는 규칙으로 변경 되었으며 나눗셈의 결과는 언제나 0 방향으로 잘려 나가기 때문에, 5 / -3
의 결과는 항상 -1
가 될 수 있도록 변경 되었다. (음수 기준에선 0 이 더 크므로, 0 방향으로 잘린다 means that 몫이 더 큰 방향의 결과를 산출한다)
정답: 딱
RAND_MAX
만큼,ANSI C
선에서 정리 가능
They were originally written as macros:
#define toupper(c) ((c) + 'A' - 'a')
#define tolower(c) ((c) + 'a' - 'A')
This assumption is valid for both the ASCII
and EBCDIC
character sets, and probably isn't too dangerous, because the nonportability of these macro definitions can be encapsulated in the single file that contains them.
These macros do have one disadvantage, though: when given somthing that is not a letter of the appropriate case, they return garbage.
He considered rewriting the macros this way:
#define toupper(c) ((c) >= 'a' && (c) <= 'z' ? (c) + 'A' - 'a' : (c))
#define tolower(c) ((c) >= 'A' && (c) <= 'Z' ? (c) + 'a' - 'A' : (c))
but realized that this would cause c
to be evaluated anywhere between one and three times for each acll, which would play havoc with expressions like toupper(*p++)
.
Instead, he decided to rewrite toupper
and tolower
as functions. The toupper()
function now looked something like this:
int toupper(int c)
{
if (c >= 'a' && c <= 'z')
return c + 'A' - 'a';
return c;
}
#define _toupper(c) ((c) + 'A' - 'a')
#define _tolower(c) ((c) + 'a' - 'A')
Choose what you'd prefer!
There was just one problem in all this: the people at Berkeley never followed suit, nor did some other C implementers.
오늘도 말 안 듣는 버클리 대학교. 이래서 표준이 많은 것 같기도 하고...
The seventh edition of the reference manual for the UNIX system described slightly different behavior:
Realloc changes the size of the block pointed to by ptr to size bytes and returns a pointer to the (possibly moved) block. The contents will be unchanged up to the lesser of the new and old size.
Realloc also works if ptr points to a block freed since the last call of malloc, realloc, or calloc; thus sequences of free, malloc, and realloc can exploit the search strategy of malloc to do storage compaction.
In other words, this implementatoin allowed a memory area to be reallocated after it had been freed, as long as that reallocation was done quickly enough.
Needless to say, this technique is not recommended, if only because not all C implementations preserve memory long enough after it has been freed. However, the Seventh Edition manual leaves one thing unstated: an earlier implementation of realloc
actually required that the area given to it for reallocatoin be freed first.
요약:
free()
한거 건들지마라. 하지 말라면 하지 말라고 이 XXXX야.
진짜 당연한 말이지만Undefined Behavior
이다.
void printnum(long n, void (*p)())
{
if (n < 0) {
(*p)('-');
n = -n;
}
if (n >= 10)
printnum(n/10, p);
(*p) ((int) (n % 10) + '0');
}
This program, for all its simplicity, has several portability problems.
n
to character form. (ANSI C
이래로, decimal digit 문자에 대해선 그 값이 1씩 증가해야 함을 guarantee 하고 있다.)n = -n
might overflow, because 2's complement machines generally allow more negative values than positive values to be represented.void printneg(long n, void (*p)())
{
if (n <= -10)
printneg(n/10, p);
(*p)("0123456789"[-(n % 10)]);
}
void printnum(long n, void (*p)())
{
if (n < 0) {
(*p)('-');
printneg(n, p);
} else
printneg(-n, p);
}
This still doesn't quite work. We have used n/10
and n%10
to represent the leading digits and the trailing digit of n
(with suitable sign changes).
void printneg(long n, void (*p)())
{
long q;
int r;
q = n / 10;
r = n % 10;
if (r > 0) {
r -= 10;
q++;
}
if (n <= -10)
printneg(q, p);
(*p)("0123456789"[-r]);
}
This looks like a lot of work to cater to portability.
two's complement 는 단 한번도 생각해본적이 없는데... 지식이 늘었다.
https://www.mythos-git.com/Cruzer-S/CTAP/-/tree/main/Chapter07
[Book] C Traps and Pitfalls (Andrew Koenig)
[Book] C: A Reference Manual 5/e (Samuel P. Harbison III, Guy L. Steele Jr.)
[Book] C Programming: A Modern Approach 2/e (K.N.King)
[Site] ISO/IEC 9899:1999, 5.2.1/3 (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)
[Site] Cambridge Dictionary, https://dictionary.cambridge.org/
[Site] 네이버 영한사전, https://dict.naver.com/