【独学するコンピュータサイエンス】ポインタのサイズとビット
静的記憶
自動変数とは異なり、宣言ブロック到達時の変数領域の開放や確保は行われません。更にプログラム実行中は常に変数のメモリを占有し続けます。ですから静的変数は本当に必要な時のみ使用します。
値を宣言するときには、静的変数にするのか、自動変数にするのかをしっかりと判断しましょう。
ポインタのサイズ
型に限らず、任意のポインタのサイズはそのCPUのレジスタサイズになります。ですので、
<例>
#include <stdio.h>
int main(void) {
int n; /* nはint型変数 */
int *ip; /* ipはint型のポインタ変数 */
double x; /* xはdouble型変数 */
double *dp; /* dpはdouble型のポインタ変数 */
/* それぞれの変数のメモリサイズを表示 */
printf("size of int : %d\n", sizeof(int));
printf("size of (int *) : %d\n", sizeof(int *));
printf("size of double : %d\n", sizeof(double));
printf("size of (double *): %d\n", sizeof(double *));
return 0;
}
(実行結果の例)
size of int : 4
size of (int *) : 4
size of double : 8
size of (double *): 4
char型は1バイト、short型は2バイト、long型は4バイト
間接参照演算子
*ポインタを表す間接参照演算子をポインタ変数に作用させると、ポインタ変数に格納されたアドレスに存在するデータを参照するようになります。
ビット単位の論理積
ビット単位の論理積は特定のビットを 0 にする時に用います。
ビット単位の論理和
ビット単位の論理和は特定のビットを 1 にする時に用います。
ビット単位の排他的論理和
B の値に注目して先の表を書き換えます。
A ^ B
A B 結果
0 0 0
1 0 1
0 1 1
1 1 0
B の値が 0 の場合を考えます。
A が 0 の時、結果は 0 です。
A が 1 の時、結果は 1 です。
B の値が 0 の場合、 結果は A の値そのままです。
B の値が 1 の場合を考えます。
A が 0 の時、結果は 1 です。
A が 1 の時、結果は 0 です。
B の値が 1 の場合、結果は A の値が反転したものになります。
よって特定ビットを反転させる場合に排他的論理和演算は用いられます。
単純なプログラムで暗号化と復号化も可能です。これもビットが反転することの応用です。
/* 34_02.c ビット単位の排他的論理和を利用した文字配列の暗号化と復号化 */
#include <stdio.h>
/* プロトタイプ宣言 */
void en_decipher( unsigned char s[], const int sz, const unsigned char key );
void std_out( const unsigned char s[], const int sz );
/* マクロ定義 */
#define Mac_Key 0x35
int main( void )
{
const unsigned char key = Mac_Key;
unsigned char str[] = "ABC竹薮焼けた1234";
const int max = sizeof( str );
printf( "最初\n%s\n", str );
std_out( str, max );
puts( "\n暗号化後" );
en_decipher( str, max, key ); /* 暗号化 */
std_out( str, max );
puts( "\n復号化後" );
en_decipher( str, max, key ); /* 復号化 */
std_out( str, max );
printf( "\n%s\n", str ); /* 元に戻った文字列を表示 */
return ( 0 );
}
/* データを 1 バイトずつ十六進数で標準出力へ出力 */
void std_out( const unsigned char s[], const int sz )
{
int i;
for ( i = 0; i < sz; i++ )
{
printf( "%02X, ", s[ i ] );
}
puts( "" );
return;
}
/* 暗号化と復号化 */
void en_decipher( unsigned char s[], const int sz, const unsigned char key )
{
int i;
for ( i = 0; i < sz; i++ )
{
s[ i ] ^= key;
}
return;
}
暗号化と復号化は共通の関数で共通の処理をしています。s[ i ] ^= key; たったこの一行で一文字の暗号化と復号化が行えます。
0 ^ 1 は 1 です。1 ^ 1 は 0 です。右の項を定数にします。A が 0 の時、 0 ^ A は 0 、 1 ^ A は 1 。 A が 1 の時はそれぞれ 1 、 0 です。
ある値 X に対して ある値 A との排他的論理和を取ります。X ^ A の結果を Y とします。 Y ^ A とすると X になります。つまり同じ値で二回ビット単位の排他的論理和を求めると最初の値に戻るのです。
ビット単位の論理否定
JIS 名称オーバーライン、一般にチルダと呼ばれる記号を用います。
演算前に整数拡張が行われます。
一の補数は全ビットが反転した状態になるので、ビット単位の論理否定と同じ効果が得られます。
~A
A 結果
0 1
1 0
ビットが反転する単純明快な動作です。項そのものの値に変化はありません。広義での型変換演算子であり、~ 直後の項の値を整数拡張の後ビット単位で反転し、その結果を式の値として持ちます。型は整数拡張後のものです。
これを用いると特定ビットを 0 にする記述にも応用出来ます。
34_00.c に於いて、小文字化する時に文字コードと 0xDF とを & 演算していました。 0xDF は第 5 ビットだけを 0 にした 1101 1111(2) を予め計算したものです。第 5 ビットをクリアーする値を求めるのなら最初に第 5 ビットだけを立てた値を考えます。 0010 0000(2) == 0x20 ですね。 0x20 に対して一の補数を求めます。するとビットが反転し 1101 1111(2) になります。その値と文字コードとのビット単位の論理積を求めるのです。つまり#define Mac_ToBig( (unsigned char)0xDF ) /* 小文字→大文字 /この行を#define Mac_ToBig( (unsigned char)( ~0x20 ) / 小文字→大文字 */にするのです。演算が増えて処理速度が低下しないかと心配するかも知れません。しかし直値の演算は可能ならコンパイルの時点で演算を済ませた値にコンパイラーが最適化してしまいます。従ってどちらの表記でも問題無いでしょう。勿論どちらを使うかは自由です。
C 言語では二進数を直接記述出来ない
なので、ビット演算をおこなう際には、16進数に変換するのが基本です