型修飾子(Type Qualifiers)

データの扱いに特定の属性を付与するものとして [型修飾子] がある. 型修飾子には const, volatile, restrict の三つある.

const int c = 1;

といった場合, オブジェクト c は const 修飾型 (const-qualified type) であり, その型は const int である.

また,

const int m[8];

の場合, const 修飾型 const int の配列 (各要素が const 修飾型) であり, 配列 (式) m 自体は const 修飾型ではない.

ポインタの場合, 例えば:

const int *p;

int *const cp;

のポインタ p は const 修飾型 const int へのポインタでその型は const int * であるが, ポインタ cp は非修飾型 int へのポインタであり, ポインタ cp 自体が const 修飾型となる.

const

const 修飾型は次の文法を持つ:

非 const 修飾型の左辺値によって const 修飾型のオブジェクトを変更する場合, 未定義の動作となる.

const 修飾型は, 変更可能な左辺値 (modifiable lvalue) ではない

数学の言葉を使えば [定数] ということである. 処理系によっては (volatile を付さない) const 修飾型は読み込み専用の記憶域領域 (read-only of storage) に割り当てられることがある.

const1.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>

int main(void){
  double x = 1.0, y = 2.0;

  const double pi = 3.14159;
  
  pi = x + y; // コンパイルエラーとなる

  return 0;
}

const1.c の実行結果は:

[cactus:~/code_c/c_tuts]% gcc -Wall -O2 -o const1 const1.c
const1.c: In function ‘main’:
const1.c:8: error: assignment of read-only variable ‘pi’

const2.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
#include <stdio.h>
#include <string.h>

void myPrint(const char *, size_t);

int main(void){
  char str[8] = "ABCDE";
  char x[16] = "0123";

  myPrint(x, sizeof(x));
  strncat(x, str, sizeof(x) - strlen(x) - 1);
  myPrint(x, sizeof(x));
  strncpy(x, str, sizeof(x) - 1);
  myPrint(x, sizeof(x));

  return 0;
}

void myPrint(const char *str, size_t size){
  const char *s;
  printf("str: SIZE = %zd, LENGTH = %zd\n", size, strlen(str));
  for(s = str; *s != '\0'; s++)
    printf("[%p]\tx[%td]\t%c\t(%d)\t%s\n", s, s - str, *s, *s, s);
}

const2.c の実行結果は:

str: SIZE = 16, LENGTH = 4
[0x7fff5fbff3a0]	x[0]	0	(48)	0123
[0x7fff5fbff3a1]	x[1]	1	(49)	123
[0x7fff5fbff3a2]	x[2]	2	(50)	23
[0x7fff5fbff3a3]	x[3]	3	(51)	3
str: SIZE = 16, LENGTH = 9
[0x7fff5fbff3a0]	x[0]	0	(48)	0123ABCDE
[0x7fff5fbff3a1]	x[1]	1	(49)	123ABCDE
[0x7fff5fbff3a2]	x[2]	2	(50)	23ABCDE
[0x7fff5fbff3a3]	x[3]	3	(51)	3ABCDE
[0x7fff5fbff3a4]	x[4]	A	(65)	ABCDE
[0x7fff5fbff3a5]	x[5]	B	(66)	BCDE
[0x7fff5fbff3a6]	x[6]	C	(67)	CDE
[0x7fff5fbff3a7]	x[7]	D	(68)	DE
[0x7fff5fbff3a8]	x[8]	E	(69)	E
str: SIZE = 16, LENGTH = 5
[0x7fff5fbff3a0]	x[0]	A	(65)	ABCDE
[0x7fff5fbff3a1]	x[1]	B	(66)	BCDE
[0x7fff5fbff3a2]	x[2]	C	(67)	CDE
[0x7fff5fbff3a3]	x[3]	D	(68)	DE
[0x7fff5fbff3a4]	x[4]	E	(69)	E

この例はポインタに const を利用したケースである. 関数 myPrint 内で受け取った配列 str が変更できないようにすう. 関数 myPrint 内のポインタ str や s は const char へのポインタであり, ポインタ自体は const 修飾型ではない!!!!

volatile

volatile 修飾型は, 次の文法を持つ:

非 volatile 修飾型の左辺値によって volatile 修飾型のオブジェクトを参照する場合, 未定義の動作となる.
volatile 修飾型は, 処理系に不明な形で値が変更される可能性を持ち, それへのアクセスは処理系依存となる.

volatile は該当の変数に処理の最適化をしないようにコンパイラに知らせるためのもの. コンパイラは処理の最適化を試み, 一部の変数を CPU のレジスタに割り当てたり, 文を削除することがある.

volatile1.c

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(void){
  int i;
  for(i = 0; i < 5; i++)
    printf("%d\n", i);

  return 0;
}

volatile1.c の実行結果は:

[cactus:~/code_c/c_tuts]% ./volatile1
0
1
2
3
4

gdb を使って逆アセンブル disassemble の結果を見てみる:

(gdb) disassemble main
Dump of assembler code for function main:
0x0000000100000ee0 <main+0>:      push   %rbp
0x0000000100000ee1 <main+1>:      mov    %rsp,%rbp
0x0000000100000ee4 <main+4>:      push   %rbx
0x0000000100000ee5 <main+5>:      sub    $0x8,%rsp
0x0000000100000ee9 <main+9>:      xor    %esi,%esi
0x0000000100000eeb <main+11>:     lea    0x3a(%rip),%rdi        # 0x100000f2c
0x0000000100000ef2 <main+18>:     xor    %eax,%eax
0x0000000100000ef4 <main+20>:     callq  0x100000f26 <dyld_stub_printf>
0x0000000100000ef9 <main+25>:     mov    $0x1,%ebx
0x0000000100000efe <main+30>:     xchg   %ax,%ax
0x0000000100000f00 <main+32>:     mov    %ebx,%esi
0x0000000100000f02 <main+34>:     lea    0x23(%rip),%rdi        # 0x100000f2c
0x0000000100000f09 <main+41>:     xor    %eax,%eax
0x0000000100000f0b <main+43>:     callq  0x100000f26 <dyld_stub_printf>
0x0000000100000f10 <main+48>:     inc    %ebx
0x0000000100000f12 <main+50>:     cmp    $0x5,%ebx
0x0000000100000f15 <main+53>:     jne    0x100000f00 <main+32>
0x0000000100000f17 <main+55>:     xor    %eax,%eax
0x0000000100000f19 <main+57>:     add    $0x8,%rsp
0x0000000100000f1d <main+61>:     pop    %rbx
0x0000000100000f1e <main+62>:     leaveq
0x0000000100000f1f <main+63>:     retq
End of assembler dump.

次の例では, 最適化によって while 文全体が削除されている. (i は 0 なので, 繰り返しの条件は常に偽. コンパイラは while 文の処理あ無駄であると考えたため削除になる)

volatile2.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>

int main(void){
  int i = 0;
  while(i){
    printf("%d\n", i);
    i = 0;
  }

  printf("%d\n", i);

  return 0;
}

volatile2.c の実行結果は:

[cactus:~/code_c/c_tuts]% ./volatile2
0
[cactus:~/code_c/c_tuts]% gdb volatile2
GNU gdb 6.3.50-20050815 (Apple version gdb-1469) (Wed May  5 04:36:56 UTC 2010)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin"...Reading symbols for shared libraries .. done

(gdb) disassemble main
Dump of assembler code for function main:
0x0000000100000f10 <main+0>:      push   %rbp
0x0000000100000f11 <main+1>:      mov    %rsp,%rbp
0x0000000100000f14 <main+4>:      xor    %esi,%esi
0x0000000100000f16 <main+6>:      lea    0x17(%rip),%rdi        # 0x100000f34
0x0000000100000f1d <main+13>:     xor    %eax,%eax
0x0000000100000f1f <main+15>:     callq  0x100000f2e <dyld_stub_printf>
0x0000000100000f24 <main+20>:     xor    %eax,%eax
0x0000000100000f26 <main+22>:     leaveq
0x0000000100000f27 <main+23>:     retq
End of assembler dump.

restrict

restrict は C99 より導入された新しい修飾型である. その文法は次の通りである.

restrict 修飾型ポインタによってアクセスされるオブジェクトは, そのオブジェクトへのアクセスがすべてそのポインタに限定される.

ポインタとオブジェクトへのアクセスが (1 対 1) 対応になるということ.

これは malloc によって返却されたポインタとオブジェクトの関係と同じように, 他のオブジェクトとポインタに 1 対 1 対応を持たせるというものである.

restrict 修飾型の応用は, 標準ライブラリ関数に見られる:

int fprintf(FILE * restrict stream, const char * restrict format, ...);

int printf(const char * restrict format, ...);

int snprintf(char * restirct s, size_t n, const char * restrict format, ...);

Table Of Contents

Previous topic

プログラムの分割とリンク

Next topic

ACSII チャート (ASCII コード表)