2
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C言語 忘れがちな仕様メモ

Last updated at Posted at 2019-11-28

はじめに

久々にC言語で開発ってなった時に忘れていたことをメモします(基礎的なものばかりです)

String(文字列)

char型変数の宣言

C言語でchar型を宣言すると、1文字ずつの文字列がarrayとして格納されるイメージ。

char str[6] = "hello";

実質6文字となる。文字列の最後がNULL埋めされるため。
上記のような宣言の場合、コンパイラーがNULL埋めしてくれる。

下記のように宣言しても同じ。

char str[6] = {'h', 'e', 'l', 'l', 'o', '\0'};

重要なのは、宣言したい変数の長さは、NULLを考慮し最低+1の長さを指定しておく必要があること。

Tipsとしては、実は変数の長さを指定せずに変数宣言をしたとしても自動的に文字列+NULLで長さを決めてくれる。

char str[ ] = "world";  // size 6

C言語char型の罠

忘れてはいけないのが、C言語ではchar型array変数を定義するとポインターを作ったのと同じになる。
ここさえマスターすればC言語はなんとかいけるはず。

char a;         // メモリサイズ:1byte
char *b;        // メモリサイズのわかっていないchar型の配列
char c[16];     // メモリサイズ:16byteの配列

b = a;          // NG : アドレスに実体を代入しているため
b = &a;         // OK : 実体aのアドレスを代入しているため
b = c;          // OK : 同一の型なので、OK。しかし渡しているのはcの先頭アドレス
b = &c;         // NG : 'char *'型に 'char (*)[4]'を代入している
b = &c[0];      // OK : cの先頭インデックスのアドレスを代入

自身が一番わかりにくいと思ったのがb = &cの例。
下記はincompatible pointer typeと言われる。

NG.c
int main(){
  func_a{char *a){
      printf("s",a);
  }

  char a[4] = "aaa";
  func_a(&a);
  return 0;
}

こうだとOK。char a arrayのアドレスを渡しているので。
追記。C言語のこの仕様はほぼおまじない。
実際は関数受け側は*はとしてポインタになっているが、実際はchar a[4]の実体を渡しているということ。

OK.c
int main(){
  func_a{char *a){
      printf("s",a);
  }

  char a[4] = "aaa";
  func_a(a);
  return 0;
}

char型arrayの関数渡し

これはさらに混乱する仕様。完璧には理解できていないので、ご指摘いただけると助かります
二次元の場合でindexごとに値を変更したい場合は下記。

  1. &foo[i]とすることでindexのアドレスを関数に渡す

もし下記コードのようにfunc_a’内にてchar型arrayを変更し、
func_a以降でfooを使用したい場合。

  • 関数呼び出し時は&でアドレス値を渡す(&をつける場合はindex指定のときだけ。)
  • 関数の受け側(func_a)はchar**でfooを受け取る必要がある (関数呼び出し元のアドレスを返す必要があるため。
    *で関数の受け側で変数を受け取っても、別アドレスを指してしまう。そのため、関数呼び出し元の後処理で変更後の変数を使えない)
  • 関数内でfooを変更する場合はchar*にする(関数で受け取った実体の値を変更する。返却するアドレスは**になっているので関数呼び出し元のアドレスを返却できる)
int func_a(char**); //ダブルポインターで宣言する必要あり
//func_bのprototypeは省略
int main() {
  char foo[4][4];
  for(int i=0; i<4; i++) {
    //func_aでfooの値をmodifyする
    return_code = func_a(&foo[i]);
    //以降でfooを使用する
    func_b(&foo[i]);
  }
}

int func_a(char** array) {
  //process
  //*にすることでfooの実体を書き換えることができる
  memcpy(*array, str, sizeof(array));
  return return_code;
}

便利な文字列操作の標準関数


#include <string.h>

strlen(str)          // strの長さを取得 (NULL文字はカウントしない)
strcat(str1,str2)    // 2つの文字列を結合
strcpy(str1,str2)    // str2をstr1にコピー
strlwr(str)          // 小文字に変換
strupr(str)          // 大文字に変換
strrev(str)          // 文字列を逆に並び替える
strcmp(str1,str2)    // 2つの文字列を比較、一致した場合は0を返す
strncat(str1,str2,n) // str2のn文字数str1にセット
strncpy(str1,str2,n) // str2のn文字数str1にコピー
strncmp(str1,str2,n) // str2のn文字数をstr1と比較、一致した場合は0を返す
strchr(str,c)        // cの文字列がstrにあるかチェック、見つかった時点でのポインタを返す
strrchr(str,c)       // cの文字列をstrの逆から検索、見つかった時点でのポインタを返す
strstr(str1,str2)    // str2の文字列がstr1にあるかチェック、見つかった時点でのポインタを返す

##sprintf() と sscanf()

###sprintf()
sprintfは異なる型の変数をひとまとめにしたい時に便利。

  char info[100];
  char name[] = "foo";
  int age = 10;
  sprintf(info, "%s is %d years old.", name, age);
  printf("%s\n", info); //foo is 10 years old

###sscanf()
sscanfは1つの文字列の変数からデータをバラしたい時に便利。

  char info[ ] = "Jan 1st 2020";
  char birth_month[10];
  char birth_day[10];
  int birth_year;
  sscanf(info, "%s %s %d", birth_month, birth_day, &birth_year);
  printf("I was born on %d, %s, %s.", birth_month, birth_day, birth_year); //I was born on Jan, 1st, 2020

Array(配列)

二次元配列

よく忘れるけど最初の[]が配列数、そのあとが長さ。

char test[3][5] = {
  "abc",
  "def",
  "ghi"
};

長さ指定が面倒な時は下記。

char *test[] = {
  "abc",
  "def",
  "ghi"
};

値の取得は下記の通り。

printf("%s\n", test[0]); //abc
printf("%s\n", test[1]); //def
printf("%s\n", test[2]); //ghi

配列要素数の求め方

int array = [10,20,30,40,50];
int sz = sizeof(array) / sizeof(array[0];
printf("size: %d",sz); //size:5 (int型は4byte, (4*5)/4となる)

sizeof(array)が配列全体のサイズ。sizeof(array[0]が配列1要素分の大きさ

Function Pointer

ポインタに関数を持たせることもできる。

#include <stdio.h>
void multiply(int num1,int num2); // function

int main() {
    void (*funptr)(int, int);     // function pointer
    funptr = multiply;            // pointer assignment
    funptr(3,3);                  // function call
    
    return 0;
}

void multiply(int num1,int num2) {
    printf("result:%d\n", num1*num2); // result:9
}

void (*funptr)(int, int) = multiplyとしても実装できる。
funcptr = &multiplyでももちろんOK。

arrayとして関数ポインタを実装させることもできる。
sum, subtract, multiply, divideの4関数が宣言されている前提とする。4関数はint2つをパラメーターとし、intをreturnする。

int result
int (*arrayptr[4])(int,int);
arrayptr[0] = sum;
arrayptr[1] = subtract;
arrayptr[2] = multiply;
arrayptr[3] = divide;

for (i=0; i<4; i++){
  result = arrayptr[i](3,3);
  printf("result:%d",result);
  // result:6
  // result:0
  // result:9
  // result:1
}

Structure(構造体)

typedef

よく、これなんで使われてる?ってなるんですが、これが無いと構造体の変数を宣言する際に
struct foo st1;ってしないといけないところを、foo st1;だけで宣言できるという利点があります。

typedef struct {
  int a;
  char b[40];
  float c; 
} foo;

foo st1;
foo st2; 

あとはtypedefで宣言された構造体を違う構造体にメンバとして入れちゃうこともできます。

typedef struct {
  int x;
  int y;
} point;

typedef struct {
  float radius;
  point center;
} circle; 

int main (){
  circle c1 {1.1, 1,2} //メンバ初期化。イメージ的にはこう->{1.1 {1,2}}
  printf("%f","%d", "%d", c1.radius,c1.x,c1.y); //1.100000, 1,2
  return 0;
}

Union (共用体)

Structureと何が違うの?って疑問が一番最初にきます。
メモリの使い方が違います。
共用体は全てのメンバのアドレスが同じになります。(構造体はそれぞれのメンバのアドレスは異なります。)
そのため、共用体のあるメンバ変数に値がアサインされた時、その時のメモリロケーションは他メンバ変数に値がアサインされるまで保持されます。
下記サンプルコードであれば、i_numf_numprintfは意味を成しません。(最後のstrのアサインによって前のアサインが上書きされているため)

union uni {
  int i_num;
  float f_num;
  char str[10]; 
};
  
union uni test;

test.i_num = 123;
test.f_num = 12.34;
strcpy(test.str, "foo");

printf("%d\n", test.i_num); // garbage data
printf("%f\n", test.f_num); // garbage data
printf("%s\n", test.str);

メモリマネージメント

C言語には、必要なんですよね。。。
サンプルコードは、メモリ確保した後、free()をしていません。(怠惰です、すみません)
これはダメです!メモリ確保後は必ず確保したメモリを解放すること

malloc

#include <stdlib.h>を記述することで使用できます。
メモリ上に指定したサイズ分を確保する変数です。

#include <stdlib.h>

int *ptr;
ptr = malloc(5 * sizeof(*ptr)); // int=4byte分のメモリブロックを5つ作成する

if (ptr != NULL) {
  *(ptr + 2) = 50;              // 3つめのintブロックに値をアサインする
}

free

確保したメモリを解放する。

free(ptr);

##calloc
構造体におけるメモリ確保に使うことが多いっぽい。何メモリブロック作るのかの指定が可能。

typedef struct {
  int num;
  char *test;
} sample;

sample *samp;
int block_num = 3;
int i;
char str[ ] = "abc def ghi";

samp = calloc(block_num, sizeof(sample));  // Sample構造体のポインターsampにsample構造体3ブロック分のメモリ確保
if (samp != NULL) {
  for (i = 0; i < block_num; i++) {
    (samp+i)->num = i;                     // それぞれのblockの構造体メンバnumにiの値をアサイン
    (samp+i)->test = malloc(sizeof(str));  // 構造体メンバのchar型testポインターにstr変数メモリ分のメモリを確保
    strcpy((samp+i)->test, str);           // それぞれのblockの構造体メンバtestにstrの値をアサイン
  }
} 

realloc

mallocで作ったメモリスペースを拡張、修正したいときに使用

#include <stdlib.h>

int *ptr;
ptr = malloc(5 * sizeof(*ptr));   // int=4byte分のメモリブロックを5つ作成する

if (ptr != NULL) {
  *(ptr + 2) = 50;                // 3つめのintブロックに値をアサインする
}
ptr = realloc(20 * sizeof(*ptr)); // やっぱりint=4byte分のメモリブロックを20個作成する
*(ptr + 15) = 100;

文字列のメモリ確保について

malloc時、sizeofは使用せずにstrlenを使う。(文字列の長さ+1するの忘れそう。。)

char str[20];
char *pstr = NULL;

strcpy(str, "hello");
pstr = malloc(strlen(str) + 1); // +1することを忘れずに。。
strcpy(pstr, str);
printf("%s", pstr);             // hello 

exit関数

C言語は公式には例外処理をサポートしていない。
そのため時々exit関数が使われる。
EXIT_SUCCESSおよびEXIT_FAILUREstdlib.hをincludeすることで使用できる。

#include <stdio.h>
#include <stdlib.h>
int main() {
  int i1 = 1;
  int i2 = 0;

  if (i2 != 0)
    printf("i1 / i2 = %d", i1/i2);
  else {
    printf("One of the values is 0. Program exiting.");
    exit(EXIT_FAILURE);
  }
  return 0;
} 

Include

<>で囲めばライブラリの中のheaderファイルの定義。
""で囲めばユーザー定義のheaderファイルの定義。

#include <stdio.h>
#include “custom_util.h” 

##Predefined Macro definitions

char curr_time[10];
char curr_date[12];

strcpy(curr_time, __TIME__);             // 現在時刻をstringで返却
strcpy(curr_date, __DATE__);             // 現在日時をstringで返却
printf("%s %s\n", curr_time, curr_date); 
printf("This is line %d\n", __LINE__);   // 行数をintで返却
2
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?