1. 程式人生 > >陣列名和陣列名取地址的區別

陣列名和陣列名取地址的區別

以下程式碼會打印出什麼樣的日誌呢?

#include <stdio.h>

int a[2] = {1,2};
int main(){
        printf("a = %p\n", a); // I
        printf("&a = %p\n", &a); // II
        printf("a + 1 = %p\n", a + 1);// III
        printf("&a + 1 = %p\n", &a + 1);// IV

        return 0;
}
本機(linux)結果輸出:
a = 0x804a014
&a = 0x804a014
a + 1 = 0x804a018
&a + 1 = 0x804a01c

沒錯,上面I 和 II打印出來的地址是一樣的,IV 要比 III 大4個位元組的地址空間。下面是我對這一現象的解釋,如有不妥的地方請各位大蝦一定給於指出:

首先引用《C和指標》p141中的理論

在C中, 在幾乎所有使用陣列的表示式中,陣列名的值是個指標常量,也就是陣列第一個元素的地址。 它的型別取決於陣列元素的型別: 如果它們是int型別,那麼陣列名的型別就是“指向int的常量指標“。
看到這裡我想應該就知道為什麼 會有I 和 III式的結果了。

對於II 和 IV 則是特殊情況,在《C和指標》p142中說到,在以下兩中場合下,陣列名並不是用指標常量來表示,就是當陣列名作為sizeof操作符和單目操作符&的運算元時。 sizeof返回整個陣列的長度,而不是指向陣列的指標的長度。 取一個數組名的地址所產生的是一個指向陣列的指標,而不是一個指向某個指標常量的指標。
所以&a後返回的指標便是指向陣列的指標,跟a(一個指向a[0]的指標)在指標的型別上是有區別的。

然後我們用符號表和彙編程式碼來看看編譯器到底是怎樣區分&a 和 a, 並將其轉換為彙編程式碼的


通過 nm a.out 得到符號表如下:
。。。。。。。// 省略了一些與本主題無關的變數
0804a01c A _edata
0804a024 A _end
080484ec T _fini
08048508 R _fp_hw
080482bc T _init
08048330 T _start
0804a014 D a // a 變數儲存在虛擬地址0x0804a014 中
0804a01c b completed.7021
0804a00c W data_start
0804a020 b dtor_idx.7023
080483c0 t frame_dummy
080483e4 T main // main函式的地址
         U [email protected]
@GLIBC_2.0

呼叫gcc -S xx.c得到彙編程式碼:

	.file	"name_of_array.c"
.globl a
	.data
	.align 4
	.type	a, @object
	.size	a, 8 // 從這裡我們便知道sizeof(a) 等於8
a:
	.long	1 // 從這裡可以看出,編譯器直接把 .c檔案中的int 轉化為long型
	.long	2
	.section	.rodata
.LC0:
	.string	"a = %p\n"
.LC1:
	.string	"&a = %p\n"
.LC2:
	.string	"a + 1 = %p\n"
.LC3:
	.string	"&a + 1 = %p\n"
	.text
.globl main
	.type	main, @function
main:
	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	subl	$16, %esp
	movl	$.LC0, %eax // I 所對應的彙編程式碼
	movl	$a, 4(%esp)
	movl	%eax, (%esp)
	call	printf
	movl	$.LC1, %eax // II 所對應的彙編程式碼
	movl	$a, 4(%esp)
	movl	%eax, (%esp)
	call	printf
	movl	$.LC2, %eax // III 所對應的彙編程式碼
	movl	$a+4, 4(%esp)
	movl	%eax, (%esp)
	call	printf
	movl	$a+8, %edx // IV 所對應的彙編程式碼
	movl	$.LC3, %eax
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
	movl	$0, %eax
	leave
	ret
	.size	main, .-main
	.ident	"GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
	.section	.note.GNU-stack,"",@progbits
I所對應的彙編程式碼 movl $a, 4(%esp)
$表示取地址,通過符號表我們知道a對應地址為0x0804a014, 所以這段程式碼將會列印0x0804a014。但是我們明明在程式碼裡寫的是printf("a = %p\n", a), (如果a不為陣列名而是一般意義的int變數,相應的彙編碼應為movl a, 4(%esp) 怎麼編譯後的彙編程式碼會是對a取地址呢? 本人猜測為編譯器自動給a 加了一個取值符,從而翻譯為$a。
結論: 對於使用者沒有明確給出&的編碼,編譯器翻譯自動給變數a加上取值符$, 其中取a的地址得到的指標型別由陣列元素決定。

II 略過

III movl $a+4, 4(%esp)
對a加上取值符得到$a,因為陣列元素型別為int,所以指標每次需要移動四個位元組的地址空間。 所以c程式碼 a + 1 翻譯為彙編 $a + 4 

IV  movl $a+8, %edx 
所對應使用者程式碼為printf("a = %p\n", &a + 1), 根據《C和指標》中的理論,當a前面有&操作符時,編譯器將會把a對應符號表中的地址看作指向陣列的指標,sizeof(a) 為8,
從而&a + 1 將會翻譯為$a + 8
結論: 對於使用者明確給出&的編碼,編譯器將會把取a的地址得到的指標型別看作指向陣列的指標。

總結:編譯器通過使用者是否給出&,來決定指標變數的型別,進而翻譯為相應的彙編碼。 或者換句話說,&符只是用來表明變數a取地址後得到的值,被看作什麼型別的指標,而不是用來表示對a進行取地址操作。

相關推薦

C語言的陣列對陣列名地址

相信不少的C語言初學者都知道,陣列名相當於指標,指向陣列的首地址,而函式名相當於函式指標,指向函式的入口地址。現在又這樣一個問題,如果對陣列名取地址,那得到的會是什麼呢?很多人立刻會想到:給指標取地址,就是指標的指標,既二級指標嘛!當然這樣的結論是錯誤的,不然這篇筆記也就沒有

指標 陣列 陣列陣列地址區別

首先指標陣列 和陣列指標叫法本身是種誤導 指標陣列:array of pointers,即用於儲存指標的陣列,也就是陣列元素都是指標 陣列指標:a pointerto an array,即指向陣列的指標 其次他們本質一個是陣列,一個是指標 int* a[4]  

陣列陣列地址& 的區別

       當陣列是二維陣列時,陣列名array、array[0]、&array[0][0]以及陣列名取地址&在數值上是相同的,同樣各個之間的顆粒度不同。其中array[0]以及 &array[0][0] 的顆粒度相同,均是以一個數組元素為顆粒度,所以它們加1後,地址加4;而陣列名

陣列陣列地址區別

以下程式碼會打印出什麼樣的日誌呢? #include <stdio.h> int a[2] = {1,2}; int main(){ printf("a = %p\n", a); // I printf("&a = %p

陣列陣列地址符的區別(瞭解指標)

問題發現在int *p[4]和int (*ip)[4]的區別? 為了明白這個區分,做個小例子驗證 以下是驗證程式碼         int *p[4]; int a=0; int *q=NULL; p[0]= &a; p[1]=q; int (*ip)[4];

c語言——陣列&+陣列區別

   在學習c語言的過程中我們可以發現一個一維陣列的的陣列名往往具有兩層含義:  一. 作為陣列名代表整個陣列  二. 作為指標代表陣列的首元素地址(因此很容易把陣列和指標混為一談)。當陣列名作為首元素的地址時:int main() { int arr[5]; int*p

陣列(Array)列表(ArrayList)有什麼區別?什麼時候應該使用 Array 而不是 ArrayList?

下面列出了 Array 和 ArrayList 的不同點: Array 可以包含基本型別和物件型別,ArrayList 只能包含物件型別。 Array 大小是固定的,ArrayList 的大小是動態變化的。 ArrayList 提供了更多的方法和特性,比如:addAll(),removeAl

oracle 例項服務以及資料庫區別

一、資料庫名 什麼是資料庫名? 資料庫名就是一個數據庫的標識,就像人的身份證號一樣。他用引數DB_NAME表示,如果一臺機器上裝了多全資料庫,那麼每一個數據庫都有一個數據庫名。在資料庫安裝或建立完成之後,引數DB_NAME被寫入引數檔案之中。格式如下: DB_NAME=myorcl ... 在 建立

陣列(Array)列表(ArrayList)有什麼區別?什麼時候應該使用Array而不是ArrayList?

Array:它是陣列,申明陣列的時候就要初始化並確定長度,長度不可變,而且它只能儲存同一型別的資料,比如申明為String型別的陣列,那麼它只能儲存S聽型別資料 ArrayList:它是一個集合,需要先申明,然後再新增資料,長度是根據內容的多少而改變的,Arr

字串的陣列形式指標形式宣告及其區別 整理版(摘自《C Primer Plus 中文版第六版》第11章及黑馬程式設計師2018C語言提高深入淺出ch1-5 )

本節內容需要掌握記憶體分割槽的概念,可以參見:C程式的記憶體分割槽(節選自黑馬訓練營day1) 下面來看例程: #include <stdio.h> #include <stdlib.h> #include <string.h> #define sTring "I

jQuery裏面click、this事件遇到(Django模型裏for)相同的idclass想獲取值

相同 技術分享 簡化 不能 png 要求 clas http color 遇到的原型是這樣的!下面我把它簡化一下; click事件: 在瀏覽器裏面只能獲取橫線上面的值,和下面的第一個值!! 這是因為id等級比class高,而且js要求id不能重復! 當jQu

MVC 在action方法中獲取當前action的控制器action

ted col 控制 name pre value values class context 如何在某個action方法中獲取它所在的控制器和action名稱呢。 string controllerName = Request.RequestContext.RouteDa

MySQL數據庫無法大寫的問題

區分大小寫 edi win 情況 字段值 mes sql 使用 odi lower_case_table_names參數詳解: 其中 0:區分大小寫,1:不區分大小寫 MySQL在Linux下數據庫名、表名、列名、別名大小寫規則是這樣的: 1、數據庫名與表名是嚴格區分大小寫

陣列a陣列b中的內容進行交換(陣列一樣大)

1.首先定義兩個大小一致的陣列a和b, 2.建立一個臨時變數作為交換的媒介 程式碼如下: #include <stdio.h> #include <stdlib.h> int main() { int a[]={1,2,3,4}; //[]空的意思是根據陣列中的內容自

編寫一個使用指標的c函式,交換陣列a陣列b中的對應元素

#include<stdio.h> #include<stdlib.h>   void ex(int *a, int *b, int n){ int i,tmp; for (i=0;i<n;i++){ tmp=a[i];a[i]=b

PHP使用字串傳遞變數函式

<?php function showTime($val = null) { if ($val) { return time(); } else { return 'param error!'; } } $para

如何遍歷資料庫中的資料(在不知道資料庫的情況下)

    玩轉資料庫的元資訊操作,主要是兩個類:  * 1) DatabaseMetaData類: 包含驅動資訊、資料庫名、表名(檢視、儲存過程等)  *     通過con.getMetaData()可獲得一個DatabaseMetaData類物件  * 2) ResultS

獲取呼叫自己方法的使用者的包

此功能可以獲得第三方的對自己功能的使用情況,可以知道非法操作! //下面程式碼段是獲取呼叫自己方法的人的包名和類名 1.比如一個叫MainActivity內部呼叫了這個初始化 MainActivity中有呼叫:ABC.getInstance(); 結果:name:com.xx.Mai

view administrator頁面上計算機DNS不匹配

問題: 因為某些原因將原來Windows版的VCenter換成了VCSA,並重新將環境配置成原來一樣。在重新配置了VIEW Administrator上Vcenter後,在管理頁面卻出現了計算機名和DNS名不匹配的情況。具體情況如下圖 影響: 現了計算機名和DNS名不匹配的情況可能會

js中避免函式變數跟別人衝突(1)

方法1: (function(){ var m = 0; var n = 1; function getName(){ return m;