C語言學習筆記:結構體與指標
結構體
結構體定義
與C++不同的是,C語言中定義結構體必須加上關鍵字struct
,而C++中結構體是一個類的關鍵字,故可以不加。
struct persin{
char name[20];
int age;
char gender;
float height;
}
訪問方式
直接引用
通過.
實現
struct person yu;
yu.age = 20;
間接引用
通過->
實現
struct person *ptr = &yu;
ptr->age = 20;
結構體的記憶體分佈
不一定是結構體內元素的簡單相加,
我們認為,結構體中的記憶體分佈應該如上圖左圖所示,但是實際上是右圖,由於結構體中存在記憶體對齊原則。
記憶體對齊原則
1.按照結構體中所佔位元組最大的資料元素進行對齊,每次申請記憶體只能是這個最大記憶體的整數倍。
2.而且結構體每個成員相對結構體首地址的記憶體偏移量都是該成員大小的整數倍。
3.結構體首地址,也是最大記憶體的整數倍。
上圖例子中,int和float都佔4個位元組,故每次至少申請4個位元組記憶體大小。
舉例說明:
struct node1{
char a;
char b;
int c;
}
struct node2{
char a;
int c;
char b;
}
上圖中,node1
佔用八個位元組,第一次申請四個位元組的區域,兩個char
型別變數都可以存入這四個位元組,餘下兩個位元組不夠存入int
型別變數故再開闢四個位元組記憶體,總共開闢
2
∗
4
=
8
2*4=8
2∗4=8個位元組。
而node2
佔用十二個位元組,第一次申請四個位元組的區域儲存一個char型別變數,餘下三個位元組不夠儲存int故再開闢一個4位元組記憶體,且int
需要儲存在偏移量為4整數倍的位置,故佔用新開闢的這四個位元組,而最後一個char
變數需要新開一片四位元組記憶體儲存,故總共開闢
3
∗
4
=
12
3*4=12
3∗4=12個位元組。
對齊值
編譯器是按照什麼方式進行對齊?
1.資料型別自身的對齊值,即自身所佔記憶體大小
2.結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。
3.指定對齊值:#pragma pack (value)時的指定對齊值value。
4.資料成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。
當一個成員的對齊值確定為 N N N後,該成員所存放地址必須滿足 起 始 地 址 % N = 0 起始地址 \% N=0 起始地址%N=0。
#pragma pack (1) /*指定按1位元組對齊*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復預設對齊*/
//大小為 7個位元組, 空地址
共用體(聯合體)
各個欄位共用一片空間,空間大小為欄位中最大的那一個。
舉例 :P轉整數
#include<stdio.h>
union IP{
struct{
unsigned char a1;
unsigned char a2;
unsigned char a3;
unsigned char a4;
} ip;
unsigned int num;
};
int main(){
union IP p;
char str[100] = {0};
int arr[4];
while(~scanf("%s", str)){
sscanf(str, "%d.%d.%d.%d", arr, arr + 1, arr + 2, arr + 3);
p.ip.a1 = arr[0];
p.ip.a2 = arr[1];
p.ip.a3 = arr[2];
p.ip.a4 = arr[3];
printf("%u\n", p.num);
}
return 0;
}
輸出結果與預期不符,改動最後一位整形數字變太大。
//輸入192.168.0.1
16820416
//輸入192.168.0.2
33597632
小端模式和大端模式
大端模式和小端模式是cpu兩種不同的儲存模式
小端:數字低位存在地址低位
大端:數字低位存在地址高位
上圖結果是由於在小端機中,IP第一位存在與地址低位。而轉int時,IP第一位192被作為數字低位,而最後一位2被作為數字高位。
在主函式中進行修改
int main(){
union IP p;
char str[100] = {0};
int arr[4];
while(~scanf("%s", str)){
sscanf(str, "%d.%d.%d.%d", arr, arr + 1, arr + 2, arr + 3);
p.ip.a1 = arr[3];
p.ip.a2 = arr[2];
p.ip.a3 = arr[1];
p.ip.a4 = arr[0];
printf("%u\n", p.num);
}
return 0;
}
輸出結果正常
//輸入192.168.0.1
3232235521
//輸入192.168.0.2
3232235512
本機位元組序和網路位元組序
判斷大端機和小端機
int is_little(){
static int num = 1;
return ((char*)(&num))[0];
}
//返回1 則為小端機, 否則大端機
地址與指標
地址可以運算元據的本源:
地址
int 型別在記憶體中的儲存方式:
不同位數的作業系統的區別,就是記憶體定址能力不同。32位作業系統只能識別4GB記憶體,因為只有32個bit位。64位系統使用64bit,是一個不可能達到的上限。
機器給每一個位元組一個地址標號,變數的地址都是首地址,比如上圖變數a
,首地址為0x187c20
。
指標變數
指標變數是也是變數,其儲存的值是地址,其自身也有地址。指標型別決定指向變數的型別。
同一個系統中,指標變數的大小都相同,指標型別決定定址偏移量。
*運算子的結合
*
指標運算子的結合方式,跟變數結合
int a;
int (*p) = &a;
//通過編譯
int a;
(int*) p = &a;
//不通過編譯
多級指標,編譯時變數結合
int a;
int (*p) = &a;
int (**pp) = &p;
//通過編譯
但邏輯上理解為,先定義一個指標變數p
,指向int
型別的變數a
的地址,在定義一個指標變數pp
,指向一個int*
型別的指標變數p
。
&取地址運算子
在C語言中,&a
表示取出變數a
的首地址,只能作用於存在於記憶體中的變數或者結構體。
只有位於記憶體中的變數可以取地址,位於暫存器中的變數不可以取地址。
int a = 1;
int* pa = &a;//編譯通過
int* pa2 = &(a+1); //編譯不通過, a+1在暫存器中
*解引用運算子
*
還可以作為對指標變數解引用,用於通過指標間接訪問一個變數。
舉例
struct Data{
int x,y;
}
struct Data a[2];
struct Data * p = a;
//多種方式表示 a[1].x
a[1].x
(*(a + 1)).x
(a + 1)->x
(p + 1)->x
(*(p + 1)).x
p[1].x
(&p[1])->x
(&p[0] + 1)->x
(*(&p[0] + 1)).x
地址偏移量
struct Data{
int a;
double b;
char c;
}
上圖例子中結構體struct Data
的對齊值為8個位元組,int
型別變數a
與偏移量0位置,double
型別變數b
與偏移量8位置,char
型別變數c
與偏移量16位置。
//計算偏移量巨集定義offset
#define offset(T, a) ({\
T* temp;\
(char*)&(temp->a)-(chat*)temp;\
})
//更便捷的寫法
#define offset(T, a) (long)(&(((T*)NULL)->a))
指標定義陷阱
不要用巨集定義重新命名指標型別
#define pchar char*
typedef char* ppchar;
int main(){
pchar c1,c2;
//c2為一個char而不是char*
//展開程式碼為 char* c1,c2;
ppchar c2,c4;
//c3,c4均為char*
}
函式指標
定義函式指標,*
必須跟指標變數走,否則為函式宣告。
int (*add)(int, int);//定義函式指標
int *add(int, int);//宣告返回指標變數的函式
使用typedef命名函式指標
typedef
用法:
//內建型別重新命名
typedef long long lint;
typedef char * pchar;
//結構體型別重新命名
typedef struct __node{
int x, y;
}Node, *PNode;
//函式指標命名:
typedef int (*func)(int);
//func可以接收任何傳入int返回int的函式
main函式引數
//作業系統給main函式傳參
int main();
int main(int argc, char *argv[]);
int main(int argc, char *argv[], char **env);
//返回值給作業系統
argc
儲存引數個數,argv
儲存字串陣列(個數為argv),env
儲存環境變數。
測試引數
void output(int argc, char *argv[], char **env){
printf("argc = %d\n");
for (int i = 0; i < argc; i++){
printf("argc[%d] = %s\n", i, argv[i]);
}
printf("\n\n);
//通過空地址作為結束條件遍歷環境變數
for (int i = 0; env[i]; i++){
printf("env[%d] = %s\n", i, env[i]);
}
}
int main((int argc, char *argv[], char **env){
output(argc, argv, env);
return 0;
}
命令列輸入
./a.out hello world
輸出
argc = 3
argv[0] = ./a.out
argv[1] = hello
argv[2] = world
env[0] = USER=jasonhuang
env[1] = LOGNAME=jasonhuang
env[2] = HOME=/home/jasonhuang
env[3] = PATH=/home/jasonhuang/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
env[4] = MAIL=/var/mail/jasonhuang
env[5] = SHELL=/usr/bin/zsh
env[6] = SSH_CLIENT=202.204.169.80 47785 22
env[7] = SSH_CONNECTION=202.204.169.80 47785 172.17.0.15 22
env[8] = SSH_TTY=/dev/pts/0
env[9] = TERM=xterm
env[10] = XDG_SESSION_ID=34430
env[11] = XDG_RUNTIME_DIR=/run/user/1001
env[12] = LANG=en_US.utf8
env[13] = SHLVL=1
env[14] = PWD=/home/jasonhuang/C_train/struct_p_union
env[15] = OLDPWD=/home/jasonhuang
env[16] = ZSH=/home/jasonhuang/.oh-my-zsh
env[17] = PAGER=less
env[18] = LESS=-R
env[19] = LSCOLORS=Gxfxcxdxbxegedabagacad
env[20] = LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
env[21] = AUTOJUMP_SOURCED=1
env[22] = AUTOJUMP_ERROR_PATH=/home/jasonhuang/.local/share/autojump/errors.log
env[23] = _=/home/jasonhuang/C_train/struct_p_union/./a.out
舉例:實現執行使用者限制
可以通過環境變數USER
,限制執行該程式的使用者
#include<string.h>
#include<stdlib.h>
void (char **env){
for (int i = 0; env[i]; i++){
if(!strcmp(env[i], "USER=", 5)){
if(!strcmp(env[i]+5, "your_USER_NAME")){
printf("Welcome, USER_NAME");
}
else{
printf("You have no access, Please go out!");
exit(0);
}
}
}
}
相關文章
- c語言野指標與結構體指標動態記憶體分配小解C語言指標結構體記憶體
- C語言指標筆記C語言指標筆記
- C語言學習筆記之指標的運算C語言筆記指標
- C語言指標學習C語言指標
- C語言學習之:指標與字串C語言指標字串
- Solidity語言學習筆記————15、結構體StructSolid筆記結構體Struct
- Go語言學習筆記 - PART7 - 結構體Go筆記結構體
- C 語言學習筆記筆記
- C語言學習筆記C語言筆記
- C語言學習筆記--C運算子C語言筆記
- go 語言指標學習Go指標
- C語言 指標與陣列C語言指標陣列
- c語言學習筆記===函式C語言筆記函式
- C語言指標總結大學霸IT達人C語言指標
- go語言學習-結構體Go結構體
- C語言指標C語言指標
- 指標學習筆記指標筆記
- C++ 學習筆記(3):引用和指標C++筆記指標
- 初識C語言(01)—學習筆記C語言筆記
- C語言學習筆記——位運算C語言筆記
- c語言程式基礎學習筆記C語言筆記
- C語言學習筆記之變數C語言筆記變數
- C++語言程式設計筆記 - 第6章 - 陣列、指標與字串C++程式設計筆記陣列指標字串
- 如何系統學習C 語言(中)之 結構體篇結構體
- c語言基礎筆記DAY7_結構體共用體列舉C語言筆記結構體
- GO 學習筆記->結構體Go筆記結構體
- C語言程式設計讀書筆記:結構C語言程式設計筆記
- C語言_資料結構圖(留個筆記)C語言資料結構筆記
- C語言指標和陣列筆試題C語言指標陣列筆試
- 嵌入式C語言學習筆記2C語言筆記
- C 語言結構體記憶體佈局問題結構體記憶體
- C語言結構體記憶體佈局問題C語言結構體記憶體
- C語言 函式指標C語言函式指標
- C語言基礎-指標C語言指標
- c語言指標彙總C語言指標
- C語言指標用法大全C語言指標
- C語言指標(二) 指標變數 ----by xhxhC語言指標變數
- Oracle體系結構學習筆記Oracle筆記