C++で詰まったことのメモ
C++にて、詰まったことをいろいろメモしていきます。
随時更新
最終更新 : 2024/5/26
見慣れない変数宣言
sdk周りのソースコードで出てきた。
以下のような形式で型 変数名(value)
の形で初期化変数宣言をすることができる。
int test01(234);
float test02(2.45);
std::cout << "test00 : " + std::to_string(test00) << std::endl;
std::cout << "test01 : " + std::to_string(test01) << std::endl;
struct周り
structの宣言とtypedef
structは基本
struct _Book{
int a;
float b;
double c;
};
みたいな形で宣言され、以下のように初期化される。
//初期化
struct _Book book1 = {
4,
2.0,
3.0
};
//
struct _Book book2;//
structは型名を作れるものではないので、structというタグを最初につけなくてはいけない。
ただ、typedefを使えば、型名のように初期化することができる。
typedef struct _Book _book; //struct _Bookを_bookとして扱う。
//別パターンとして以下のようにすれば_bookという名前でtypedefできる。
typedef struct{
int a;
float b;
double c;
}_book;
//そうするとこういけるので楽。
_book book1;
structのメモリ管理
sizeof()に構造体変数を渡すと、想定より大きな値が返されるることがある。
構造体は各メンバのための領域が連続して確保される。しかし、処理効率やエラー回避のためのパディングと呼ばれる隙間を空けて、構造体の領域が確保されることがある
このパディング領域を含めた領域のサイズを返すため、大きくなる。
typedef struct{
short s;
int i;
} x;
この場合、shortで2byte、intで4byte使うため、処理系や環境によって異なるが64bitでメモリを管理する環境だとするなら以下のように64bitをまたぐように変数を管理すると色々問題が起こる。
https://www.mm2d.net/main/legacy/c/c-15.html
このメモリバス幅が意味していることは、 CPU がメモリから情報を読み出す場合 8Byte 単位で扱っているということで、 それより細かい単位でのアクセスはできないということになります (取り出すときの指定ポインタの精度が 8Byte 単位であるというイメージ)。
さて、 CPU にとってメモリアクセスというのは非常に遅い処理なので、できる限り避けたい処理です。 ところが、たとえばint型変数がメモリ上に以下のように配置されてしまった場合、 この変数の値を取り出すにはメモリアクセスが本来なら1回ですむはずが、2回必要になってしまいます。 またレジスタに格納する際、ビットシフトを行い結合させるなどの作業が必要となってきます。 (CPUによってはこのようなメモリ配置をしようとすると落ちてしまうものもあるらしいです。)
[引用] https://www.mm2d.net/main/legacy/c/c-15.html
そのため、メモリアクセスの配置方が決められている。
https://www.mm2d.net/main/legacy/c/c-15.html
person_t 構造体のメンバのメモリサイズを全て足し合わせると 41 であるのに, person_t 型の変数のメモリサイズは 44 となっている. その理由は,コンピュータのアクセス速度を速めるために, アクセスに都合の良い位置にメンバを配置するためである. その結果,メンバとメンバの間に隙間(パディングと呼ぶ)ができて,全体のメモリサイズが大きくなる.
https://www.cc.kyoto-su.ac.jp/~yamada/programming/struct.html
checksumやるときにこれを知らなくて一致しなくて困った。
structを関数の引数に使う
structを引数にもつ、関数が以下のような値渡しでの引数の場合、
実引数の構造体のメンバの値がそれぞれコピオーされて、関数の本体に渡される。
//Carというstructだとして
int main(){
Car c1;//これが structだとして
myfunc(c1);
}
void myfunc(Car c){
std::cout << c.num << std::endl;
}
構造体のポインタを引数に渡す場合は
構造体型の変数のアドレスを使うようになるので、値渡しと違ってたくさんのメンバーをコピーするのではなくて、アドレスだけが渡されるため早い。
int main(){
myfunc(&c1);
}
ポインタの場合でメンバにアクセスする場合は、->(アロー関数)を使わなくてはならない。
void myfunc(Car* pc){
std::cout << pc->num << std::endl;
}
構造体の参照を引数に渡す場合の関数
int main(){
myfunc(c1);
}
参照の場合は、.
でアクセスできる。
void myfunc(Car &pc){
std::cout << pc.num << std::endl;
}
C++でのstructの挙動
C++ではCと違って、関数を作ることもできる。
となると一見してClassとほぼ変わらなくなるが、実際そんなに変わらない。
違いとしてデフォルトのアクセシビリティ(public, private)が違うだけ。
structはpublic, classはprivateになっている。
あとは、実際ユーザーがデータ構造っぽいイメージかclassっぽいイメージかという使い分けになる。
Switch文の仕様
Switch文の中で変数定義・宣言をしようとすると、エラーを吐かれる。
参考
[C言語] switch文の中の変数の定義がエラーとなる
#include <stdio.h>
int main(void) {
int i;
for (i = 0 ; i < 100 ; i++) {
switch (i%2) {
case 0:
char msg_even[] = "EVEN";
printf("%3d is %s\n", i, msg_even);
break;
case 1:
char msg_odd[] = "ODD";
printf("%3d is %s\n", i, msg_odd);
break;
default:
printf("%3d is UNKNOWN\n", i);
break;
}
}
return 0;
}
- 原因
C言語の場合、switch文が『一つのブロック・スコープ』であるため、スコープの途中で変数宣言を行うことができない。
C++の場合、case文を『jumpラベル』として扱うため、case文に飛ぶ(jumpする)前に全ての変数の初期化が終わっていなければならない。
[引用] https://www.chihayafuru.jp/tech/index.php/archives/2990
goto + ラベルについて
(第5章 飛んでいきな)[http://www7b.biglobe.ne.jp/~robe/cpphtml/html03/cpp03005.html]
よくわかっていないが、とにかくswtichの中ではそのままでは宣言できないこととなっている。
なので、どうしてもやりたい場合はswitchの中に{}
を書いてブロックを作ることで回避できる。
#include <stdio.h>
int main(void) {
int i;
for (i = 0 ; i < 100 ; i++) {
switch (i%2) {
case 0:
{
char msg[] = "EVEN";
printf("%3d is %s\n", i, msg);
}
break;
case 1:
{
char msg[] = "ODD";
printf("%3d is %s\n", i, msg);
}
break;
default:
printf("%3d is UNKNOWN\n", i);
break;
}
}
return 0;
}
以下のような例でこの事例にぶつかった
ss << で0でfillしたかったが、それが宣言扱いになってしまった。
switch(clipDigit){
case 0:
{
ss << std::setw(2) << std::setfill('0') << clipCounter;
std::string s1(ss.str());
clipname = "C" + s1;
}
break;
case 1:
{
ss << std::setw(3) << std::setfill('0') << clipCounter;
std::string s2(ss.str());
clipname = "C" + s2;
}
break;
サイズと型
C言語では整数型のサイズが処理系定義になっているため、予期せぬエラーが発生する可能性がある。
C99から別途サイズ固定の型を追加された。
#include <cstdint>
(C++標準)or stdint.h
(C言語標準)のインクルード必要
<cstdint>ヘッダでは、ビット数が規定された整数型の別名、およびマクロを提供する。これらの機能は、std名前空間に属することを除いてC言語の標準ライブラリ<stdint.h>ヘッダと同じである。
[引用] https://cpprefjp.github.io/reference/cstdint.html
8bit | 8bit | |
---|---|---|
符号 | あり | なし |
型名 | int8_t | uint8_t |
備考 | 一般のsigned char相当 | 一般のunsigned char相当 |
16bit | 16bit | |
---|---|---|
符号 | あり | なし |
型名 | int16_t | uint16_t |
備考 | 一般のsigned short相当 | 一般のunsigned short相当 |
32bit | 32bit | |
---|---|---|
符号 | あり | なし |
型名 | int32_t | uint32_t |
備考 | 一般のsigned int相当 | 一般のunsigned int相当 |
64bit | 64bit | |
---|---|---|
符号 | あり | なし |
型名 | int64_t | uint64_t |
備考 | 一般のsigned long相当 | 一般のunsigned long相当 |
charは処理系定義
charがunsigned charかsigned charかはその処理系によってどっちかに決まる。
どっちか判断するには、 #include <limits.h>
を使う。
#include <limits.h>
には#defineで環境依存のものがマクロで書かれているみたいで、自分の環境では以下のようになった。
std::cout << "char min : " << CHAR_MIN << std::endl; //-128
std::cout << "char max : " << CHAR_MAX << std::endl; //127
std::cout << "char bit : " << CHAR_BIT << std::endl; //8
参考
char と int の変換キャストの留意点 - C++ プログラミング
24bit補数表現 整数/実数を扱いたい
FreeD1 protocolを扱いたい時にこの問題がおきた。
24bit & 補数表現整数を作りたいという場面
方法1
ビットフィールド宣言を使って行う方法
#include <cstdint>
// 24ビット符号付き整数を表現する構造体
struct Int24 {
int32_t value : 24;
//ビットフィールド宣言
// コンストラクタ
Int24(int32_t val) : value(val) {}
// 暗黙の型変換
operator int32_t() const {
return value;
}
};
// 使用例
Int24 a = -8388608; // -2^23
Int24 b = 8388607; // 2^23 - 1
Int24 c = a + b; // -1
ビットフィールドの宣言で使用できる型は_Bool、signed int、unsigned intです。この例ではunsigned intを使っていて、その日が猛暑日であったかどうかなどを1ビットで表せるようにしてあります。ビットフィールドのメンバーへのアクセスは、ビットフィールドではない構造体のメンバーと同じように “.”演算子を利用します。
[引用]https://atmarkit.itmedia.co.jp/ait/articles/1204/26/news140_2.html
方法2
camikura/touchdesigner-shotoku-vr-chop
上のgithubで行われていた方法
shotokuというVRクレーンシステムで使われているFreeD1のプロトコルのための実装となっている。
touchdesigner-shotoku-vr-chop/ShotokuVRCHOP.cpp
に実際の処理が書かれている。
hexToInt()
が実際の処理定義部分
int hexToInt(unsigned char d1, unsigned char d2, unsigned char d3)
{
int v = (
_lrotl(d1, 24) & 0xff000000 |
_lrotl(d2, 16) & 0x00ff0000 |
_lrotl(d3, 8) & 0x0000ff00);
return v / 0x100;
//ここで0x100で割る意味は、最下位byteを単純に削るという意味。
//最初から24bitをビットシフトで入れない理由は歩数表現の処理をint(32bit)にやらせるため
}
void readRotation(unsigned char data[29]) {
auto rx = this->hexToInt(data[5], data[6], data[7]) / 32768.0 + this->rotate[0];
auto ry = this->hexToInt(data[2], data[3], data[4]) / 32768.0 + this->rotate[1];
auto rz = this->hexToInt(data[8], data[9], data[10]) / 32768.0 + this->rotate[2];
this->chanValues[3] = rx;
this->chanValues[4] = ry;
this->chanValues[5] = rz;
}
C++において value/0x100 は、変数 value の値を16進数表記で表現した場合に、下位2桁を除いた上位桁の値を表します。具体的には、 0x100 は16進数で256に相当するため、この式は value の値を256で割り、整数除算(切り捨て)した結果を返します。つまり、 value の下位2桁を除いた上位桁の値を得ることができます。
例えば、 value が16進数表記で 0x123456 の場合、 value/0x100 は 0x1234(10進数表記で4660)となります。
バイナリから解析
何らかの通信プロトコルを生のバイナリから解析したい場面などがあると思う。
byte order
メモとして残しておく。
ビッグエンディアン・リトルエンディアンのなどのルール
IBM汎用機、SPARC、JAVAではビックエンディアン
インテル製CPUではリトルエンディアンを使っている模様。
どちらかを示すために、BOM(byte order mark)と呼ばれるコードをデータの先頭に負荷させる場合がある。
BOMは16進数で「FEFF」だとの並びだとビッグエンディアン、「FFFE」の並びだと、リトルエンディアンになっている。
Char*に数値として得られている場合(内部表現として記録)
つまり、unsigned char* binary = 0x51
みたいな状態。
charの実態の内部表現として1byte記録されている場合。
以下のようにcout
のようなことをした場合はutf-8などの文字コードの場合、
7bitまでは ASCIIのルールに従うため
3
のような出力がされる。
std::cout << "binary : "<< binary << std::endl;
binary : 3
このunsigned char(1byte)を使って、
複数のbyteから成る32bitのint(float)のようなものを作りたいときは
以下のように、bitシフトでずらしていけば作れる。
int32_t i_byte = i_1_byte + (i_2_byte << 8) + (i_3_byte << 16) + (i_4_byte << 24);
別な章で言っているbyteからfloatを作る奴はこのintを使えばできあがる。
255
のような内部表現数値をcoutした場合
utf-8などを使っていた場合、この文字セットのルール上7bit(10進数でいう128)から、256すぎ(ちょっとあいまい)まで定義が存在しないため、
自分の環境だと\377
のように出力された。
これは8進数で表すマーク\
をつけて8進数で表示されているだけなので、露頭に迷わないこと。
Char*に文字として数値が得られている場合(not 内部表現として記録)
つまり、unsigned char* binary = "51"
のような状態。
charの内部表現ではなく、文字列として1byteが記録されている。
cout
のような標準出力で出した場合、51
と出力される。
内部表現では「5」と「1」で構成されているため、ASCIIで表現できるなら0x35 0x31
になっている。
この文字列表現を内部表現に変更するには以下のようなステップを踏む
unsigned char class_name::getUcharToBinaryUchar(unsigned char _byte){
unsigned char _i_byte = 0;
for(int i = 0; i < strlen(_byte); i++){ //文字数だけ繰り返す
if(i == (strlen(_byte)-1) ){//数字の1桁め
_i_byte += _byte[i] - '0';
}
else{//数字の2,3桁め
_i_byte += pow(10,(strlen(_byte) - i - 1)) * (_byte[i] - '0');
}
}
return _i_byte;
これで、"51"
が51(10進数)
になる。
ここではunsigned charでreturnするようにしているが、_i_byte
の部分をintにしても成り立つ。
intに同じ数値が入る。
引数がC++のstd::string
の場合もほぼ同じだが載せておく。
unsigned char class name::getStringToBinaryUchar(std::string _string){
const char* _byte = _string.c_str();
unsigned char _i_byte = 0;
for(int i = 0; i < strlen(_byte); i++){
if(i == (strlen(_byte)-1) ){//1桁め
_i_byte += _byte[i] - '0';
}
else{//2,3桁め
_i_byte += pow(10,(strlen(_byte) - i - 1)) * (_byte[i] - '0');
}
}
return _i_byte;
}
floatをbyteからつくる
色々方法があるのだろうが、共用体を使うと個人的には楽だった。
参考
浮動小数点数の内部表現を取得してみよう
浮動小数点型の数値はメモリ上でどのように格納されているのか
/* float型のデータサイズは32ビットなので、32ビット整数型( int )とコンビを組みます */
union { float f; int i; } a;
int i;
a.f = -2.5f;
printf( "%f ( %08X )\n", a.f, a.i );
/* ビットの列を表示します */
for( i = 31; i >= 0; i-- ){
printf( "%d", ( a.i >> i ) & 1 );
}
printf( "\n" );
/* 指数部( 1ビット )、指数部( 8ビット )、仮数部( 23ビット )を取り出します */
printf( "符号部 : %X\n", ( a.i >> 31 ) & 1 );
printf( "指数部 : %X\n", ( a.i >> 23 ) & 0xFF );
printf( "仮数部 : %X\n", a.i & 0x7FFFFF );
実行結果
-2.500000 ( C0200000 )
11000000001000000000000000000000
符号部 : 1
指数部 : 80
仮数部 : 200000
メモリまわり
memcpy
#include <string.h>
のインクルードする必要がある。
配列や文字列の変数については初期化以外では=で代入(コピー)できない。
ただ、配列のコピーを行いたいときはある。
C言語が提供するmemcpy
関数を使用すると配列を丸ごとコピーできる。
これは配列に限ったものではなく、メモリ領域を別のメモリ領域にコピーするという汎用的なメモリコピーに使用できる。
void* memcpy(void* addr1, const void* addr2, size_t len);
addr1 : コピー先の先頭アドレス
addr2 : コピー元の先頭アドレス
len : コピーするバイト数
return : addr1と同様
いわゆる浅いコピー(shallow copy)と言われるもの。配列や構造体自身は複製される一方、内部に持つポインタがさしている先の領域までは複製されない。つまり、コピー元とコピー先で同一のものを指し、共有してしまうという事が起きる。
headerファイル周り
ファイル分割の仕組みとしては、
1ファイルで書いている場合のプロトタイプ宣言をしていた仕組みを利用する。
#include
はヘッダファイルに書いたプロトタイプ宣言をそのまま置き換えるイメージ。
#include <iostream> //標準のヘッダファイル
#include "func001.hpp" //自分で作成したヘッダファイル
でmain関数の下にヘッダファイルとリンクするsourceファイルを貼り付けているイメージ
func001.hpp : func001周りの関数プロトタイプ宣言
func001.cpp : func001周りの関数の定義
main.cpp : main関数の定義
実際にはmain.cpp, func001.cppを別々でコンパイルして、オブジェクトファイルを作り、
このオブジェクトファイル同士をリンクさせるという手順で処理される。
関数プロトタイプ宣言を使っているため、コンパイルする時に関数の呼び出しただしいかどうかをチェックすることができる。
//プロトタイプ宣言
///ここが、#include ***で指定されたヘッダファイルに書いておく部分
int max(int x , int y);
int main (){
**********
*******
*****
}
// ヘッダでプロトタイプ宣言んされた関数とリンクするsourceファイルをここに貼り付けているイメージ
int max(int x, int y){
if(x > y){
return x;
}
else{
return y;
}
}
参照周り(reference)
参照は同じアドレスを共有している変数を増やす行為。
感覚としてはハードリンクみたいなこと。
変数宣言、引数とかのときに&
をつけると参照宣言になる。
普通の時に変数に&つけるとaddressになるから注意。
int a = 5;
int& rA = a;
std::cout << "a : " << a << std::endl;
std::cout << "rA : " << rA << std::endl;
rA = 50;
std::cout << "rAに50代入" << std::endl;
std::cout << "a : " << a << std::endl;
std::cout << "rA : " << rA << std::endl;
std::cout << "a address : " << &a << std::endl;
std::cout << "rA address : " << &rA << std::endl;
参照になっている変数同士は影響を及ぼしあう。
a : 5
rA : 5
rAに50代入
a : 50
rA : 50
a : 0x00F4
rA : 0x00F4
宣言と初期化
分けての宣言ができない。
以下みたいなことはできない。宣言と初期化は同時に行わないといけない。
int& rA;
rA = a;
引数に使う
関数の引数を参照で渡したいときは、
関数宣言の方の引数をint& x
見たいん感じでrefrence宣言にしてあげればよい。
swapなどの時がやりやすい。
void swap(int& x, int& y);
int main(){
int num1 = 5;
int num2 = 10;
swap(num1, num2);
std::cout << num1 << std::endl;
std::cout << num2 << std::endl;
}
void swap(int& , int&y){
int tmp;
tmp = x;
x = y;
y = tmp;
}
constをつけて引数
ポインタも参照も引数でconstつけて宣言すると、値の変更をできなくなすることができる。
参照やポインターだけど、意図的に値を変えてしまっているミスを回避したいときはconstつけておくとコンパイルで転けてくれるはず。
void func(const int* px);
void func(const int& rX);
static周り
local変数でのstaticをつけると
local 変数のスコープでstatic
指定子(記憶クラス指定子[storage class identifier])をつけて宣言すると、global変数と同じような記憶寿命を持つ変数として宣言できる。
静的寿命を持つローカル変数という。
プログラムの開始時にグローバル変数と同じタイミングで初期化されて、一般にプログラミング終了時に破棄される。
local (自動) : 宣言されてから関数が終了する(自動)
local (static) : プログラム実行準備から終了まで(静的)
global : プログラムの実行準備から終了まで(静的)
//プロトタイプ宣言
void func();
int a = 0;//global変数
int main(){
for(int i = 0; i < 5; i++){
func();
}
return 0;
}
void func(){
int b = 0; //func()の中のlocal変数
static int c = 0; // local変数でstaticで初期化
std::cout << "a : " << a << ", b : " << b << ",c : " << c << std::endl;
a++;
b++;
c++;
}
a : 0, b : 0, c : 0
a : 1, b : 0, c : 1
a : 2, b : 0, c : 2
a : 3, b : 0, c : 3
a : 4, b : 0, c : 4
普通local変数のポインタを返そうとすると、func()のスコープを抜けているため、意味のないアドレスになる。
staticだと最初に初期化されているものなので、func()を抜けても残っているので、意味のあるアドレスを返せる。
int* func(){
static int a = 10;
int* pA = &a;
reuturn pA;
}
int main(){
int* pRes = func();
}
リンケージ周りのスコープについて, global変数のstatic
ファイルを分割して作成しているプログラムの場合、どこのファイルや段階まで適用される変数なのかとかがわからないと困る。
global変数は全てのファイルで使うことが可能。(外部リンケージをもつ)
staticをつけるとそのファイル内だけに限定することができる。(内部リンケージを持つ)
local変数 (無指定)(static) : ブロック内のスコープ
global変数 (無指定) : 全てのファイルで使用可能(外部リンケージ)
global変数 (static) : ファイルないのみのスコープ(内部リンケージ)
関数 : static扱いでファイルないのみのスコープ(内部リンケージ)
global変数のextern
extern int a
別のファイルの(無指定)global変数を利用する時にはexternという指定子をつけたヘッダファイルを読み込んでコンパイルすることで使用可能
#include "myfunc.hpp"
int main(){
a++;
}
extern int a; //global変数を使えるようになる。
int a;
void func(){
}
namespace
c++ではnamespace(名前空間)と呼ばれる手法で関数・変数を別のファイルとぶつからないで使うことができる。
namespace Sample{
int a;
void func();
}
Sample::a = 10;
Sample::func();
classでのstatic
staticをつけた、classに関連づけられているメンバを静的メンバ(static member)と呼ばれるものは、
オブジェクトに紐づけられているのではなくて、Classに紐づけられている変数となる。
classに紐づけられているから、コンストラクタ内で初期化できるものではなくて、Car::をつけて関数外で初期化しないといけない。
staticをつけた関数の場合は、そのクラスからオブジェクトが作成されていなくても呼び出すことができる。
Car::func()
のようにCar::をつけないといけない。
ただし、staticメソッドの中ではメンバ変数にアクセスするようなことをすると意味がわからなくなるので、エラーになる。
class Car{
private:
int num;
public:
float uuid;
static int sum;
void func();
static void showSum();
}
Car::Car(){
num = 0;
sum += 1;
}
void Car::func(){
*****
****
***
}
void Car::showSum(){
std::cout << sum << std::endl;
}
int Car::sum == 0;
int main(){
Car::showSum();
}
typedefの簡単に理解
型名に他の名前をつけるというもの。
typedef 型 名前
typedef int Seisu
typedef unsigned long int ulInt
上のようなtypedefをやった後は、 以下二つは同じ意味となる。
ulInt num = 1;
unsigned long int num = 1;
Discussion