對C語言指針強轉的理解

1.什麼是表達式, 表達式的屬性是什麼

寫得好的博客貼在這裏:https://blog.csdn.net/astrotycoon/article/details/50857326

定義:概括說來表達式是由一系列運算符(operators)和操作數(operands)組成的。這既是表達式的定義,同時也指明瞭表達式的組成成份。運算符指明瞭要進行何種運算和操作,而操作數則是運算符操作的對象

屬性:任何表達式都有值和類型兩個屬性

2.左值&右值(C和指針P79)

右值不細說, 左值概括出來就是:如果能確定一個表達式最終結果的存儲位置, 那麼這個表達式可以作爲左值,需要注意的是某些表達式也可作爲左值

這一部分的練習可以參考<C和指針>P99的指針表達式

3.強制類型轉換運算符(譚浩強第四版:P56)

覺得寫得好的博客貼在下面:https://blog.csdn.net/simple_the_best/article/details/48421279

4.示例

上面藍色星星說進行強制類型運算(int)x後得到一個int類型的中間數據, 賦值後就不再存在了,應當如何理解呢?正好最近在看的項目代碼中有大量的指針強轉,結合這一點,從AT&T彙編角度看一下編譯器怎麼處理的,因爲GCC用的是AT&T彙編,所以需要參考<AT&T彙編語言>這本手冊

爲了把每條語句看清楚, 我用printf將語句隔開,看註釋就好,同時需要注意彙編中movl和leal的區別,看註釋

root@ubuntu:/lianxi/lianxi_oj/cast_pointer# gcc cast_pointer.c -m32
root@ubuntu:/lianxi/lianxi_oj/cast_pointer# ./a.out
=====1=====
=====2=====
&a = 0xbfb8e8f4
=====3=====
=====4=====
p = 0xbfb8e8f4
=====5=====
&p = 0xbfb8e8f8
=====6=====
=====7=====
q = 0xbfb8e8f4
=====8=====
&q = 0xbfb8e8fc
=====9=====
*q = 0x78
=====10=====
#include <stdio.h>
//#define DEBUG(format,...) printf(format,##__VA_ARGS__)
//#define PRINT(format,args...) printf(format,##args)

int main(int argc, char* argv[])
{
    printf("=====1=====\n");
    int a = 0x12345678;//little endian mem:low-0x78 0x56 0x34 0x12-high

    printf("=====2=====\n");
    printf("&a = %p\n", &a);

    printf("=====3=====\n");
    int* p = &a;

    printf("=====4=====\n");
    printf("p = %p\n", p);

    printf("=====5=====\n");
    printf("&p = %p\n", &p);

    printf("=====6=====\n");
    char* q = (char*)p;

    printf("=====7=====\n");
    printf("q = %p\n", q);

    printf("=====8=====\n");
    printf("&q = %p\n", &q);

    printf("=====9=====\n");
    printf("*q = %#x\n", *q);

    printf("=====10=====\n");
    return 0;
}
root@ubuntu:/lianxi/lianxi_oj/cast_pointer# gcc -S cast_pointer.c 
	.file	"cast_pointer.c"
	.section	.rodata
.LC0:
	.string	"=====1====="
.LC1:
	.string	"=====2====="
.LC2:
	.string	"&a = %p\n"
.LC3:
	.string	"=====3====="
.LC4:
	.string	"=====4====="
.LC5:
	.string	"p = %p\n"
.LC6:
	.string	"=====5====="
.LC7:
	.string	"&p = %p\n"
.LC8:
	.string	"=====6====="
.LC9:
	.string	"=====7====="
.LC10:
	.string	"q = %p\n"
.LC11:
	.string	"=====8====="
.LC12:
	.string	"&q = %p\n"
.LC13:
	.string	"=====9====="
.LC14:
	.string	"*q = %#x\n"
.LC15:
	.string	"=====10====="
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$32, %esp
	
	movl	$.LC0, (%esp)           printf("=====1=====\n");
	call	puts
	
	movl	$305419896, 20(%esp)    int a = 0x12345678;
									將立即數305419896放到(寄存器esp值+20)的值作爲地址對應的內存中
									從打印可以看出&a==0xbfb8e8f4, 所以esp寄存器的初始值應該是0xbfb8e8e0
									
	movl	$.LC1, (%esp)           printf("=====2=====\n");
	call	puts
	
	movl	$.LC2, %eax             printf("&a = %p\n", &a);
	leal	20(%esp), %edx
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
									首先&a是一個表達式, 將(寄存器esp的值+20)的值放到寄存器edx中, edx的值爲:0xbfb8e8f4
									然後再將edx的值放到(寄存器esp的值+4)的值作爲地址對應的內存中進行打印操作
									在這裏我們看到&a是一個表達式, 它的值其實是放在寄存器edx中的, 所以不能作爲左值
	
	movl	$.LC3, (%esp)           printf("=====3=====\n");
	call	puts
	
	leal	20(%esp), %eax          int* p = &a;
	movl	%eax, 24(%esp)
									同樣, &a是一個不能作爲左值的表達式, 其值0xbfb8e8f4放在寄存器eax中
									然後將eax中的值放到(寄存器esp的值+24)的值作爲地址對應的內存中
									所以p的值是0xbfb8e8f4, p的地址是0xbfb8e8f8
									在這裏我們看出p就可以作爲左值, 因爲它表示一個特定的內存位置
	
	movl	$.LC4, (%esp)           printf("=====4=====\n");
	call	puts
	
	movl	24(%esp), %edx          printf("p = %p\n", p);
	movl	$.LC5, %eax                            
	movl	%edx, 4(%esp)           
	movl	%eax, (%esp)
	call	printf
									1.首先將(寄存器esp的值+24)的值作爲地址對應的內存中的值放到寄存器edx中
									即將0xbfb8e8f8地址對應的內存內容0xbfb8e8f4放到寄存器edx中
									2.進行printf打印工作
	
	movl	$.LC6, (%esp)           printf("=====5=====\n");
	call	puts
	
	
	movl	$.LC7, %eax             printf("&p = %p\n", &p);
	leal	24(%esp), %edx   
	movl	%edx, 4(%esp)      
	movl	%eax, (%esp)  
	call	printf
									1.將(寄存器esp的值+24)的值放到寄存器edx中
									即將0xbfb8e8f8放到寄存器edx中
									2.進行printf打印工作
									
	movl	$.LC8, (%esp)           printf("=====6=====\n");
	call	puts
	
	movl	24(%esp), %eax          char* q = (char*)p;強轉重點!!!
	movl	%eax, 28(%esp)          
									1.將(寄存器esp的值+24)的值作爲地址對應的內存中的值放到寄存器eax中
									即將地址0xbfb8e8f8對應內存中的內容0xbfb8e8f4放到寄存器eax中
									2.將寄存器eax的值放到(寄存器esp的值+28)的值作爲地址對應的內存中
									所以q的地址爲0xbfb8e8fc, q的值爲0xbfb8e8f4
									所以這個過程和<譚浩強>P56中的描述一致:
									我們可以理解爲在強轉的過程中產生了一箇中間變量char* tmp(實際就是寄存器eax)
									先將p的值0xbfb8e8f4拷貝入tmp, 再將tmp的值拷貝到p中即可
									實際操作完成後tmp(eax)被系統回收, p的值和q相同, 只是類型不同
	
	movl	$.LC9, (%esp)           printf("=====7=====\n");
	call	puts
	
	movl	28(%esp), %edx          printf("q = %p\n", q);
	movl	$.LC10, %eax
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
	
	movl	$.LC11, (%esp)          printf("=====8=====\n");
	call	puts
	
	movl	$.LC12, %eax            printf("&q = %p\n", &q);
	leal	28(%esp), %edx
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
	
	movl	$.LC13, (%esp)          printf("=====9=====\n");
	call	puts
	
	movl	28(%esp), %eax          printf("*q = %#x\n", *q);
	movzbl	(%eax), %eax
	movsbl	%al, %edx
	movl	$.LC14, %eax
	movl	%edx, 4(%esp)
	movl	%eax, (%esp)
	call	printf
	
	movl	$.LC15, (%esp)          printf("=====10=====\n");
	call	puts
	movl	$0, %eax                return 0...
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
	.section	.note.GNU-stack,"",@progbits

5.補充

在本文的附件中上傳了int2short和short2int的源碼和彙編註釋

很好地解釋了強轉的規則,與文中地址博客中作者的觀點一致

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章