C語言可變數組 嵌套的可變數組,翻過了山跨過了河 又掉進了坑

可變數組

?專欄內容
postgresql內核源碼分析
手寫數據庫toadb
并發編程
個人主頁:我的主頁
座右銘:天行健,君子以自強不息;地勢坤,君子以厚德載物.

在這里插入圖片描述

概述

數組中元素是順序存放,這一特性讓我們存儲和訪問數據都很簡單,
但也因為這一特性,我們在寫代碼時,往往不能確定數組元組的個數,只能按最大的數量進行預分配,
這不僅造成了空間浪費,而且使用起來不友好,明明我們要運行一個小數據集,但卻要很多內存空間。

這就產生了可變數組,它的元素數量不需要在代碼中確定,而是在運行時確定。

實現方式

可變數組在我們的程序中經常遇到,但是它有那些實現方式呢?
根據數組存儲內存區域的不同,可以分為

  • 棧內存實現方式
  • 堆內存實現方式
    下面我們就來看看它們是如何實現,有什么不同

棧內存實現

這里C99中新增的VLA(variable-length array) 特性,可以讓我們在用的時候定義數組,數組的長度不再是靜態值,可以是變量中的值。
也就是說,數組的長度在程序編譯階段是不確定的,直到運行時再能確定,這就避夠我們定義一個最大的數組,產生很多空間浪費。

  • 舉例
void test(int n)
{/* check */if(n <= 0){return;}// int arr[n] = {0};int arr[n];/* todo  */for(int i=0; i < n; i++){arr[i] = i;}return;
}

數組arr的長度是變量n來確定

  • 注意事項
  1. 這個特性是C99引入,并不是所有的編譯器都能完全支持,我使用的 gcc 版本是支持的。
[senllang@hatch toadbtest]$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/8/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl --disable-libmpx --enable-offload-targets=nvptx-none --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix
gcc version 8.5.0 20210514 (Red Hat 8.5.0-19) (GCC)
  1. 使用VLA定義的數組,不能在定義時初始化,否則會產生以下錯誤,因為它不能使用默認的初始化器,必須由用戶自己來初始化;
[senllang@hatch toadbtest]$ gcc test.c
test.c: In function ‘test’:
test.c:9:5: error: variable-sized object may not be initializedint arr[n] = {0};^~~
test.c:9:19: warning: excess elements in array initializerint arr[n] = {0};

堆內存實現

在用的時候,通過malloc動態申請數組空間,空間大小為 數組元素類型的n倍,n是我們需要的數組大小,它可以是輸入,也可以是程序運行過程中的可變值。

這種方式是我們普遍使用的,也是所有編譯器都支持的。

  • 舉例
void test(int n)
{int *arr = NULL;/* check */if(n <= 0){return;}arr = (int *)malloc(sizeof(int)*n);if(NULL == arr){return ;}/* todo  */for(int i=0; i < n; i++){arr[i] = i;}return;
}

訪問方式

數組訪問一般有指針方式和下標方式,這與普通數組沒有什么區別,為什么要談數組的訪問方式呢? 因為這里會隱藏著驚天大坑,我們接著往下看。

C語言里一般,數組可以轉成指針,當然指針也可以轉成數組來用。

數組下標訪問

這就很簡單了,數組中的元素都是順序排列,那么按它們的位置序號訪問就可以。

對于VLA方式定義,還是動態申請方式分配的空間,它們的元素存儲的內存空間都是連續的,所以兩種方式下都可以用下標的方式來訪問。

  • 對于數組,那就再正常不過了,遞增下標就可以獲取到各元素的值;
  • 而對于動態申請的數組,本身就是指向內存空間的首地址,也可以理解為指向數組的指針,即常說的數組指針,用下標的方式就可以直接獲取到對應的元素值。
/* 如上面舉例,指針類型定義的數組,也可以下標進行訪問 */
int *arr = NULL;
arr[i] = i;

指針訪問

指針形式訪問,每次指針的移動步長,都是指針基礎類型的字節數;
此時取值時,就要以指針的方式來取值;

對于VLA方式定義,還是動態申請方式分配的空間,它們的元素存儲的內存空間都是連續的,所以兩種方式下都可以用指針的方式來訪問。

  • 對于數組,數組名就是首個元素的地址,遍歷時每次遞增+1,就會移動到下一個元素的地址;
  • 而對于動態申請的數組,本身就是指向內存空間的首地址,也是0號元素的首地址;
int testarr[n];
int *arr = testarr;for(int i = 0; i < n; i++,arr++)
{*arr = i;
}

此處專門定義一個數組,然后將數組首地址賦給指針,用指針來訪問數組元素

可變數組的嵌套使用

如果一個結構體里含有可變數組,同時結構體又存在嵌套,看起來都有點復雜,那它如何分配空間和訪問呢?

定義

假如我們定義如下結構體,最終我們使用的是 stGroupData 這個結構體;

typedef struct Postion
{int x;int y;
}stPosition, *pstPostion;typedef struct MemberData
{int posCnt;stPosition posArr[];
}stMemberData, *pstMemberData;typedef struct GroupData
{int group_id;int memberCnt;stMemberData memberData[];
}stGroupData, *pstGroupData;

大家是否好奇,上面結構的大小時多少呢?這個留給大家一個作業,知道答案的同學可以在評論區給出來。

分配空間

因為存在嵌套,所以就不能用VLA這個特性了,只能用動態分配了。
動態分配時,需要對外層結構體和內層結構體的元素分別計算,這里很容易遺漏;

假設我們有一組數據,需要2個memberdata:

memberdata 0: 有3個postion
memberdata 1: 有1個postion

坑一:占用空間

空間需要分配多少呢?

  • 可能初看好像是sizeof(stGroupData) 就可以了;
  • 再看,其實需要 sizeof(stGroupData) + 2*sizeof(stMemberData) 大小;

這就掉坑里了。下面是正確的大小計算;

計算空間大小

int size = 0;
pstGroupData pgData = NULL;/* 計算一個要分配的空間大小,假設2個memberdata:* memberdata 0: 有3個postion* memberdata 1: 有1個postion */
size = sizeof(stGroupData) + 2*sizeof(stMemberData) + 4 * sizeof(stPosition);
pgData = (pstGroupData)malloc(size);

這里計算size時,先計算結構體頭部的size,因為數組部分沒有定義長度,sizeof 出的來的值是不包含的,所以需要單獨計算;
外層stGroupData中包含兩個元素, 內層 stMemberData中分別為 3和1,也就是4個元素空間 ,再加上外層的結構體大小,就是整個所占的內存空間。
它們的內存空間分布情況,假設首地址從0開始

在這里插入圖片描述

訪問數組

那么按上面的例子,定義了一個結構體,如何訪問各個數組元素呢?
可能有小伙伴立刻就想到了下標的方式 ,那么我們來看一下

坑二:下標訪問

此時我們用下標方式引用會是正確的嗎?

pgData->memberData[0] 
pgData->memberData[1] 

memberData[0] 與 memberData[1]的地址相差,應該是一個元素的sizeof(stMemberData) = 4,也就是一個int posCnt空間大小;
從內存分布圖來看,就會變成這樣

在這里插入圖片描述

嵌套可變數組的訪問

此時下標訪問是不對的,不能采用默認的類型大小進行移動;
只能用指針方式來訪問,同時需要自己計算下一個元素的偏移大小

pstMemberData pmData = NULL;/* memberData[0] */
pmData = pgData->memberData;/* memberData[1] */
pmData = (pstMemberData)((char*)(pgData->memberData) + sizeof(stMemberData) + 3 * sizeof(stPosition));

結尾

非常感謝大家的支持,在瀏覽的同時別忘了留下您寶貴的評論,如果覺得值得鼓勵,請點贊,收藏,我會更加努力!

作者郵箱:study@senllang.onaliyun.com
如有錯誤或者疏漏歡迎指出,互相學習。

注:未經同意,不得轉載!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/35852.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/35852.shtml
英文地址,請注明出處:http://en.pswp.cn/news/35852.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【IC萌新虛擬項目】spt_core模塊基于dc的綜合環境搭建與面積時序優化

關于整個虛擬項目,請參考: 【IC萌新虛擬項目】Package Process Unit項目全流程目錄_尼德蘭的喵的博客-CSDN博客 前言 當驗證的同學正在瘋狂寫測試點,補充測試用例各種找茬找bug時候,設計的同學也要進入到跑綜合修時序優化面積的階段了。 還是老樣子,關于芯片綜合的知識就…

Redis_緩存3_緩存異常(數據不一致、雪崩、擊穿、穿透)

14.6緩存異常 四個方面 緩存中數據和數據庫不一致緩存雪崩緩存擊穿緩存穿透 14.6.1數據不一致&#xff1a; 一致性包括兩種情況 緩存中有數據&#xff0c;需要和數據庫值相同緩存中沒有數據&#xff0c;數據庫中的數據是最新值 如果不符合以上兩種情況&#xff0c;則出現…

Linux tee

tee 是一個命令行工具&#xff0c;它可以從標準輸入讀取數據&#xff0c;并將其同時輸出到標準輸出和指定的文件中。tee 命令非常實用&#xff0c;特別是在需要同時查看輸出內容和將其保存到文件中的情況下。 tee 命令的基本語法如下&#xff1a; command | tee [options] [f…

Mysql 搭建MHA高可用架構,實現自動failover,完成主從切換

目錄 自動failover MHA&#xff1a; MHA 服務 項目&#xff1a;搭建Mysql主從復制、MHA高可用架構 實驗項目IP地址配置&#xff1a; MHA下載地址 項目步驟&#xff1a; 一、修改主機名 二、編寫一鍵安裝mha node腳本和一鍵安裝mha mangaer腳本&#xff0c;并執行安裝 …

docker容器限定ip訪問

docker容器限定ip訪問 一、測試所需環境&#xff1a;二、使用docker的 iptables 策略三、Docker使用iptables 與系統Firewalld之間的關系四、沖突解決方案 一、測試所需環境&#xff1a; 主機1&#xff1a; ip&#xff1a;192.168.3.117 環境配置&#xff1a;docker、httpd(do…

你真的了解ORM嗎?通過一個簡單的例子來學習ORM

什么是ORM ORM&#xff08;Object-Relational Mapping&#xff09;是一種將面向對象程序數據模型與關系數據庫之間進行映射的技術。 比如數據庫表user&#xff0c;它有id、name、age字段映射到Java實體類就是User類&#xff0c;有id、name、age屬性。 CREATE TABLE user (id…

2023國賽 高教社杯數學建模ABCDE題思路匯總分析

文章目錄 0 賽題思路1 競賽信息2 競賽時間3 建模常見問題類型3.1 分類問題3.2 優化問題3.3 預測問題3.4 評價問題 4 建模資料 0 賽題思路 &#xff08;賽題出來以后第一時間在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 競賽信息 全國大學生數學建模…

echarts加釣魚島赤尾嶼(vue)(親測有效)

1.首先引入json文件&#xff0c;node_modules/echarts中就有 import chinaData from "../../node_modules/echarts/map/json/china.json" 2.初始化地圖&#xff0c;在初始化地圖的時候加入釣魚島和赤尾嶼的數據&#xff0c;在chinaData下的features中加入即可&#x…

Design-Pattern設計模式

Design-Pattern設計模式 圖說設計模式 圖說設計模式 在線書籍 軟件模式是將模式的一般概念應用于軟件開發領域&#xff0c;即軟件開發的 總體指導思路或參照樣板。軟件模式并非僅限于設計模式&#xff0c;還包括 架構模式、分析模式和過程模式等&#xff0c;實際上&#xff…

FFmpeg常見命令行(四):FFmpeg流媒體

前言 在Android音視頻開發中&#xff0c;網上知識點過于零碎&#xff0c;自學起來難度非常大&#xff0c;不過音視頻大牛Jhuster提出了《Android 音視頻從入門到提高 - 任務列表》&#xff0c;結合我自己的工作學習經歷&#xff0c;我準備寫一個音視頻系列blog。本文是音視頻系…

leetcode做題筆記77組合

給定兩個整數 n 和 k&#xff0c;返回范圍 [1, n] 中所有可能的 k 個數的組合。 你可以按 任何順序 返回答案。 思路一&#xff1a;直接求出組合數將每個組合放進數組中 int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {int size 0, num 1, i;in…

Rust中的智能指針:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak<T>

Rust中的智能指針是什么 智能指針&#xff08;smart pointers&#xff09;是一類數據結構&#xff0c;是擁有數據所有權和額外功能的指針。是指針的進一步發展 指針&#xff08;pointer&#xff09;是一個包含內存地址的變量的通用概念。這個地址引用&#xff0c;或 ” 指向”…

UML 類圖的畫法

1.類圖的畫法 類 整體是個矩形&#xff0c;第一層類名&#xff0c;第二層屬性&#xff0c;第三層方法。 &#xff1a;public- : private# : protected空格: 默認的default 對應的類寫法。 public class Student {public String name;public Integer age;protected I…

2023杭電第七場補題報告1002 1004 1011 1013

2023杭電第七場補題報告1002 1004 1011 1013 1002 B. Random Nim Game (hdu.edu.cn) 思路 手推一下就可以發現其實除了一次必定結束的其他情況概論都是 1 2 \frac{1}{2} 21? 代碼 #include <bits/stdc.h> using namespace std; #define int long long void solve()…

【hello C++】特殊類設計

目錄 一、設計一個類&#xff0c;不能被拷貝 二、設計一個類&#xff0c;只能在堆上創建對象 三、設計一個類&#xff0c;只能在棧上創建對象 四、請設計一個類&#xff0c;不能被繼承 五、請設計一個類&#xff0c;只能創建一個對象(單例模式) C&#x1f337; 一、設計一個類&…

Sentinel使用實例

不說了&#xff0c;直接上官方文檔 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md Sentinel Example 項目說明 本項目演示如何使用 Sentinel starter 完成 Spring Clo…

【金融量化】對企業進行估值的方法有哪些?

估值的方法有哪些&#xff1f; 如何對企業進行估值&#xff1f;有2個方法估算。 1 絕對估值法 它是一種定價模型&#xff0c;用于計算企業的內在價值。 比如說你可以根據公司近N年的現金流情況。借此去預測未來N年的現金流情況。所有的現金流數據都可以在年報上查詢到。最后…

ios 知識

IOS 類文件.h和.m中interface的區別 大家都知道我們在創建類文件時會發現&#xff1a; #import <UIKit/UIKit.h>interface ViewController : UIViewControllerend和 #import "ViewController.h"interface ViewController ()end那么他們之間有何區別呢&#x…

【Ajax】回調地獄解決方法

回調地獄&#xff08;Callback Hell&#xff09;是指在異步編程中&#xff0c;特別是在嵌套的回調函數中&#xff0c;代碼變得深度嵌套、難以閱讀和維護的現象。這通常發生在處理多個異步操作時&#xff0c;每個操作都依賴于前一個操作的結果。回調地獄使代碼變得難以理解、擴展…

顯卡服務器適用于哪些場景

顯卡&#xff08;GPU&#xff09;服務器&#xff0c;簡單來說&#xff0c;GPU服務器是基于GPU的應用于視頻編解碼、深度學習、科學計算等多種場景的快速、 穩定、彈性的計算服務。那么壹基比小鑫告訴你顯卡服務器主要的用途有哪一些。 一、運行手機模擬器 顯卡服務器可支持…