โปรดใส่ตัวอย่างพร้อมคำอธิบาย
int *p;
จะกำหนดตัวชี้ไปยังจำนวนเต็มและ*p
จะอ่านค่าตัวชี้นั้นหมายความว่ามันจะดึงข้อมูลที่ p ชี้ไปจริง ๆ
โปรดใส่ตัวอย่างพร้อมคำอธิบาย
int *p;
จะกำหนดตัวชี้ไปยังจำนวนเต็มและ*p
จะอ่านค่าตัวชี้นั้นหมายความว่ามันจะดึงข้อมูลที่ p ชี้ไปจริง ๆ
คำตอบ:
มันมักจะดีพอ - ถ้าคุณกำลังเขียนโปรแกรมการชุมนุม - เพื่อจ้องชี้ที่มีอยู่หน่วยความจำตัวเลข 1 หมายถึงไบต์ที่สองในความทรงจำของกระบวนการ 2 ที่สาม 3 ที่สี่และอื่น ๆ ....
เมื่อคุณต้องการเข้าถึงข้อมูล / ค่าในหน่วยความจำที่ตัวชี้ชี้ไปที่ - เนื้อหาของที่อยู่ด้วยดัชนีตัวเลขนั้น - จากนั้นคุณยกเลิกการอ้างอิงตัวชี้
ภาษาคอมพิวเตอร์ที่แตกต่างกันมีสัญลักษณ์ที่แตกต่างกันเพื่อบอกคอมไพเลอร์หรือล่ามว่าตอนนี้คุณสนใจในค่า (ปัจจุบัน) ของวัตถุชี้ไปที่ - ฉันมุ่งเน้นด้านล่างของ C และ C ++
พิจารณาใน C ให้ตัวชี้เช่นp
ด้านล่าง ...
const char* p = "abc";
... สี่ไบต์ด้วยค่าตัวเลขที่ใช้ในการเข้ารหัสตัวอักษร 'a', 'b', 'c' และ 0 ไบต์เพื่อแสดงถึงจุดสิ้นสุดของข้อมูลที่เป็นข้อความจะถูกเก็บไว้ในหน่วยความจำและที่อยู่ตัวเลขของสิ่งนั้น p
ข้อมูลจะถูกเก็บไว้ใน วิธีนี้ C ถอดรหัสข้อความในหน่วยความจำที่เป็นที่รู้จักกันASCIIZ
ตัวอย่างเช่นหากสตริงตัวอักษรเกิดขึ้นที่ที่อยู่ 0x1000 และp
ตัวชี้ 32 บิตที่ 0x2000 เนื้อหาหน่วยความจำจะเป็น:
Memory Address (hex) Variable name Contents
1000 'a' == 97 (ASCII)
1001 'b' == 98
1002 'c' == 99
1003 0
...
2000-2003 p 1000 hex
หมายเหตุว่าไม่มีตัวแปรชื่อ / ระบุสำหรับที่อยู่ 0x1000 p
แต่เราทางอ้อมสามารถอ้างถึงสตริงตัวอักษรโดยใช้ตัวชี้การจัดเก็บที่อยู่:
ในการอ้างถึงตัวละครp
ที่เราอ้างอิงถึงเราp
ใช้เครื่องหมายเหล่านี้ (อีกครั้งสำหรับ C):
assert(*p == 'a'); // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
// p and 1 times the size of the things to which p points:
// In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b'); // Another notation for p[1]
คุณยังสามารถย้ายพอยน์เตอร์ไปยังข้อมูลที่ชี้ไปที่, ทำการอ้างอิงตามที่คุณไป:
++p; // Increment p so it's now 0x1001
assert(*p == 'b'); // p == 0x1001 which is where the 'b' is...
หากคุณมีข้อมูลที่สามารถเขียนได้คุณสามารถทำสิ่งนี้ได้:
int x = 2;
int* p_x = &x; // Put the address of the x variable into the pointer p_x
*p_x = 4; // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4
ข้างต้นคุณต้องรู้ที่รวบรวมเวลาที่คุณจะต้องเรียกว่าตัวแปรและรหัสถามคอมไพเลอร์ที่จะจัดที่มันควรจะเก็บไว้เพื่อให้มั่นใจอยู่จะสามารถใช้ได้ผ่านx
&x
ใน C หากคุณมีตัวแปรที่เป็นตัวชี้ไปยังโครงสร้างที่มีสมาชิกข้อมูลคุณสามารถเข้าถึงสมาชิกเหล่านั้นได้โดยใช้->
โอเปอเรเตอร์ dereferencing:
typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159; // Dereference and access data member x.d_
(*p).d_ *= -1; // Another equivalent notation for accessing x.d_
ในการใช้ตัวชี้โปรแกรมคอมพิวเตอร์ยังต้องการข้อมูลเชิงลึกเกี่ยวกับประเภทของข้อมูลที่ชี้ไปที่ - ถ้าชนิดข้อมูลนั้นต้องการมากกว่าหนึ่งไบต์เพื่อเป็นตัวแทนจากนั้นตัวชี้จะชี้ไปยังไบต์ที่มีตัวเลขต่ำสุดในข้อมูล
ดังนั้นดูตัวอย่างที่ซับซ้อนกว่านี้เล็กน้อย:
double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3); // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4); // Actually looks at bytes from address p + 1 * sizeof(double)
// (sizeof(double) is almost always eight bytes)
++p; // Advance p by sizeof(double)
assert(*p == 13.4); // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8; // Change sizes[3] from 19.4 to 29.8
// Note earlier ++p and + 2 here => sizes[3]
บางครั้งคุณไม่ทราบว่าหน่วยความจำมากคุณจะต้องจนกว่าโปรแกรมของคุณกำลังทำงานและเห็นสิ่งที่ข้อมูลจะถูกโยนที่มัน ... malloc
แล้วคุณแบบไดนามิกสามารถจัดสรรหน่วยความจำที่ใช้ มันเป็นเรื่องธรรมดาที่จะเก็บที่อยู่ในตัวชี้ ...
int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10; // Dereference the pointer to the memory, then write a value in
fn(*p); // Call a function, passing it the value at address p
(*p) += 3; // Change the value, adding 3 to it
free(p); // Release the memory back to the heap allocation library
ใน C ++ การจัดสรรหน่วยความจำโดยปกติจะทำกับnew
โอเปอเรเตอร์และการจัดสรรคืนด้วยdelete
:
int* p = new int(10); // Memory for one int with initial value 10
delete p;
p = new int[10]; // Memory for ten ints with unspecified initial value
delete[] p;
p = new int[10](); // Memory for ten ints that are value initialised (to 0)
delete[] p;
ดูตัวชี้สมาร์ท C ++ด้านล่าง
บ่อยครั้งที่ตัวชี้อาจเป็นเพียงตัวบ่งชี้ว่ามีข้อมูลหรือบัฟเฟอร์บางอย่างอยู่ในหน่วยความจำ หากจำเป็นต้องใช้ข้อมูล / บัฟเฟอร์อย่างต่อเนื่องหรือความสามารถในการโทรfree()
หรือdelete
เพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำโปรแกรมเมอร์จะต้องทำงานบนสำเนาของตัวชี้ ...
const char* p = asprintf("name: %s", name); // Common but non-Standard printf-on-heap
// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
if (!isprint(*q))
*q = '_';
printf("%s\n", p); // Only q was modified
free(p);
... หรือประสานการกลับรายการการเปลี่ยนแปลงอย่างระมัดระวัง ...
const size_t n = ...;
p += n;
...
p -= n; // Restore earlier value...
free(p);
ใน C ++ เป็นวิธีปฏิบัติที่ดีที่สุดในการใช้วัตถุตัวชี้สมาร์ทในการจัดเก็บและจัดการพอยน์เตอร์โดยยกเลิกการจัดสรรโดยอัตโนมัติเมื่อตัวทำลายสมาร์ทพอยน์เตอร์ทำงาน ตั้งแต่ C ++ 11 ไลบรารีมาตรฐานจะให้สองunique_ptr
สำหรับเมื่อมีเจ้าของเดียวสำหรับวัตถุที่จัดสรร ...
{
std::unique_ptr<T> p{new T(42, "meaning")};
call_a_function(p);
// The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete
... และshared_ptr
สำหรับการเป็นเจ้าของหุ้น (โดยใช้การนับการอ้างอิง ) ...
{
auto p = std::make_shared<T>(3.14, "pi");
number_storage1.may_add(p); // Might copy p into its container
number_storage2.may_add(p); // Might copy p into its container } // p's destructor will only delete the T if neither may_add copied it
ใน C NULL
และ0
- และนอกจากนี้ใน C ++ nullptr
- สามารถใช้เพื่อระบุว่าตัวชี้ไม่ได้เก็บที่อยู่หน่วยความจำของตัวแปรในขณะนี้และไม่ควรถูกอ้างถึงหรือใช้ในการคำนวณทางคณิตศาสตร์ ตัวอย่างเช่น:
const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
switch (c) {
case f: p_filename = optarg; break;
}
if (p_filename) // Only NULL converts to false
... // Only get here if -f flag specified
ใน C และ C ++, เช่นเดียวกับชนิดของตัวเลข inbuilt ไม่จำเป็นต้องเริ่มต้นที่จะ0
ไม่bools
ไปชี้ยังไม่ได้ตั้งเสมอfalse
NULL
ทั้งหมดเหล่านี้ถูกตั้งค่าเป็น 0 / false / NULL เมื่อพวกเขาเป็นstatic
ตัวแปรหรือ (C ++ เท่านั้น) ตัวแปรสมาชิกโดยตรงหรือโดยอ้อมของวัตถุคงที่หรือฐานของพวกเขาหรือผ่านการเริ่มต้นเป็นศูนย์ศูนย์ (เช่นnew T();
และnew T(x, y, z);
ดำเนินการศูนย์ initialisation new T;
ไม่).
นอกจากนี้เมื่อคุณกำหนด0
, NULL
และnullptr
ตัวชี้บิตในตัวชี้ไม่จำเป็นต้องมีการตั้งค่าทั้งหมด: ตัวชี้อาจไม่ประกอบด้วย "0" ในระดับฮาร์ดแวร์หรือดูอยู่ 0 ในพื้นที่ที่อยู่เสมือนของคุณ คอมไพเลอร์ที่ได้รับอนุญาตในการจัดเก็บอย่างอื่นมีถ้ามันมีเหตุผลที่จะ แต่สิ่งที่มันไม่ - ถ้าคุณมาพร้อมและเปรียบเทียบตัวชี้ไป0
, NULL
, nullptr
หรือตัวชี้อื่นที่ได้รับมอบหมายใด ๆ ของผู้ที่ต้องทำงานเปรียบเทียบตามที่คาดไว้ ดังนั้นด้านล่างซอร์สโค้ดในระดับคอมไพเลอร์ "NULL" อาจเป็นบิต "เสก" ในภาษา C และ C ++ ...
พอยน์เตอร์ที่เริ่มต้นอย่างเข้มงวดยิ่งขึ้นจะเก็บรูปแบบบิตเพื่อระบุที่อยู่หน่วยความจำNULL
(หรือเสมือน )
กรณีง่าย ๆ คือกรณีนี้เป็นการชดเชยเชิงตัวเลขในพื้นที่ที่อยู่เสมือนทั้งหมดของกระบวนการ ในกรณีที่ซับซ้อนมากขึ้นตัวชี้อาจสัมพันธ์กับพื้นที่หน่วยความจำเฉพาะซึ่ง CPU อาจเลือกตามการลงทะเบียนของ "ส่วน" ของ CPU หรือลักษณะของรหัสส่วนที่เข้ารหัสในรูปแบบบิตและ / หรือค้นหาในสถานที่ที่แตกต่างกันขึ้นอยู่กับ คำแนะนำเกี่ยวกับรหัสเครื่องโดยใช้ที่อยู่
ตัวอย่างเช่นการกำหนดint*
ค่าเริ่มต้นอย่างถูกต้องให้ชี้ไปที่int
ตัวแปรอาจ - หลังจากการร่ายไปยังfloat*
- การเข้าถึงหน่วยความจำในหน่วยความจำ "GPU" ค่อนข้างแตกต่างจากหน่วยความจำที่int
ตัวแปรอยู่นั้นจากนั้นเมื่อใช้งานแล้ว หน่วยความจำที่แตกต่างกันสำหรับการถือครองเครื่อง opcodes สำหรับโปรแกรม (ด้วยค่าตัวเลขของint*
ตัวชี้แบบสุ่มและไม่ถูกต้องได้อย่างมีประสิทธิภาพภายในขอบเขตหน่วยความจำอื่น ๆ เหล่านี้)
ภาษาโปรแกรม 3GL เช่น C และ C ++ มักจะซ่อนความซับซ้อนนี้ไว้เช่น:
หากคอมไพเลอร์ให้ตัวชี้ไปยังตัวแปรหรือฟังก์ชั่นคุณสามารถยกเลิกการลงทะเบียนได้อย่างอิสระ (ตราบใดที่ตัวแปรไม่ถูกทำลาย / deallocated ในขณะเดียวกัน) และเป็นปัญหาของคอมไพเลอร์ไม่ว่าจะต้องมีการเรียกคืน คำสั่งรหัสเครื่องที่แตกต่างกันใช้
หากคุณได้รับตัวชี้ไปยังองค์ประกอบในอาร์เรย์คุณสามารถใช้เลขคณิตของตัวชี้เพื่อย้ายไปที่อื่นในอาร์เรย์หรือแม้กระทั่งสร้างที่อยู่แบบหนึ่งต่อท้ายของอาร์เรย์ที่ถูกกฎหมายเพื่อเปรียบเทียบกับตัวชี้อื่น ๆ กับองค์ประกอบ ในอาเรย์ (หรือที่ถูกย้ายโดยเลขคณิตตัวชี้ไปยังค่าเดียวกันที่ผ่านมาแบบเดียวกัน); อีกครั้งใน C และ C ++ มันขึ้นอยู่กับคอมไพเลอร์เพื่อให้แน่ใจว่า "ใช้งานได้"
ฟังก์ชั่นระบบปฏิบัติการเฉพาะเช่นการแมปหน่วยความจำที่ใช้ร่วมกันอาจให้คำแนะนำแก่คุณและพวกเขาจะ "ทำงานได้" ภายในช่วงของที่อยู่ที่เหมาะสมสำหรับพวกเขา
ความพยายามในการย้ายพอยน์เตอร์ทางกฎหมายที่เกินขอบเขตเหล่านี้หรือเพื่อส่งตัวเลขโดยพลการไปยังพอยน์เตอร์หรือใช้พอยน์เตอร์ที่พาดพิงไปยังประเภทที่ไม่เกี่ยวข้องโดยทั่วไปจะมีพฤติกรรมที่ไม่กำหนดดังนั้นควรหลีกเลี่ยงไลบรารีและแอพพลิเคชั่น อาจจำเป็นต้องพึ่งพาพฤติกรรมที่ไม่ได้กำหนดไว้โดยมาตรฐาน C หรือ C ++ ซึ่งได้กำหนดไว้อย่างดีจากการใช้งานหรือฮาร์ดแวร์เฉพาะของพวกเขา
p[1]
และ*(p + 1)
เหมือนกันหรือไม่ นั่นคือทำp[1]
และ*(p + 1)
สร้างคำสั่งเดียวกันหรือไม่
p
เป็นเพียง 2000: หากคุณมีตัวชี้อื่นp
จะต้องเก็บ 2000 ในสี่หรือแปดไบต์ หวังว่าจะช่วย! ไชโย
u
มีอาร์เรย์arr
ทั้ง gcc และ clang จะรับรู้ว่า lvalue u.arr[i]
อาจเข้าถึงที่เก็บข้อมูลเดียวกันกับสมาชิกสหภาพรายอื่น แต่จะไม่รับรู้ว่า lvalue *(u.arr+i)
อาจทำเช่นนั้น ฉันไม่แน่ใจว่าผู้เขียนคอมไพเลอร์เหล่านั้นคิดว่าหลังเรียกใช้ UB หรือที่อดีตเรียกใช้ UB แต่พวกเขาควรประมวลผลอย่างเป็นประโยชน์อยู่แล้ว แต่พวกเขาเห็นว่าสองนิพจน์แตกต่างกันอย่างชัดเจน
การยกเลิกการลงทะเบียนตัวชี้หมายถึงการรับค่าที่เก็บไว้ในตำแหน่งหน่วยความจำที่ชี้โดยตัวชี้ ตัวดำเนินการ * ใช้เพื่อทำสิ่งนี้และเรียกว่าตัวดำเนินการการยกเลิกการประชุม
int a = 10;
int* ptr = &a;
printf("%d", *ptr); // With *ptr I'm dereferencing the pointer.
// Which means, I am asking the value pointed at by the pointer.
// ptr is pointing to the location in memory of the variable a.
// In a's location, we have 10. So, dereferencing gives this value.
// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.
*ptr = 20; // Now a's content is no longer 10, and has been modified to 20.
[]
ยังระบุตัวชี้อีกครั้ง ( a[b]
ถูกกำหนดเป็นค่าเฉลี่ย*(a + b)
)
ตัวชี้คือ "การอ้างอิง" ไปยังค่า .. เช่นเดียวกับหมายเลขการโทรห้องสมุดเป็นการอ้างอิงไปยังหนังสือ "การยกเลิกการลงทะเบียน" หมายเลขการโทรกำลังดำเนินการผ่านทางกายภาพและดึงข้อมูลหนังสือเล่มนั้น
int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;
// The * causes pA to DEREFERENCE... `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4..
หากหนังสือไม่อยู่ที่นั่นบรรณารักษ์จะเริ่มตะโกนปิดห้องสมุดและมีคนสองคนตั้งค่าให้ตรวจสอบสาเหตุของคนที่จะหาหนังสือที่ไม่ได้อยู่ที่นั่น
ในคำง่าย ๆ dereferencing หมายถึงการเข้าถึงค่าจากตำแหน่งหน่วยความจำบางอย่างที่ตัวชี้นั้นชี้
รหัสและคำอธิบายจากตัวชี้พื้นฐาน :
การดำเนินการ dereference เริ่มต้นที่ตัวชี้และตามลูกศรไปเพื่อเข้าถึง pointee ของมัน เป้าหมายอาจจะดูที่สถานะปวงหรือเปลี่ยนสถานะปวง การดำเนินการยกเลิกการลงรายการบัญชีบนตัวชี้ใช้งานได้เฉพาะเมื่อตัวชี้มีจุด - จุดที่ต้องถูกจัดสรรและตัวชี้จะต้องตั้งค่าให้ชี้ไปที่มัน ข้อผิดพลาดที่พบบ่อยที่สุดในรหัสตัวชี้กำลังลืมที่จะตั้งค่า Pointee ข้อผิดพลาดรันไทม์ที่พบบ่อยที่สุดเนื่องจากข้อผิดพลาดนั้นในรหัสคือการดำเนินการไม่ทำงาน ใน Java การตรวจสอบที่ไม่ถูกต้องจะถูกแฟล็กอย่างสุภาพโดยระบบรันไทม์ ในภาษาที่คอมไพล์เช่น C, C ++ และ Pascal ความไม่ถูกต้องที่ผิดพลาดบางครั้งจะผิดพลาดและบางครั้งหน่วยความจำเสียหายในบางวิธีการสุ่ม
void main() {
int* x; // Allocate the pointer x
x = malloc(sizeof(int)); // Allocate an int pointee,
// and set x to point to it
*x = 42; // Dereference x to store 42 in its pointee
}
ฉันคิดว่าคำตอบก่อนหน้านี้ทั้งหมดผิดเพราะพวกเขาระบุว่าการลงทะเบียนหมายถึงการเข้าถึงคุณค่าที่แท้จริง Wikipedia ให้คำจำกัดความที่ถูกต้องแทน: https://en.wikipedia.org/wiki/Dereference_operator
มันทำงานกับตัวแปรตัวชี้และส่งกลับค่า l- เท่ากับค่าที่อยู่ตัวชี้ สิ่งนี้เรียกว่า "การลงทะเบียน" ตัวชี้
ที่กล่าวว่าเราสามารถอ่านค่าตัวชี้ได้โดยไม่ต้องเข้าถึงค่าที่ชี้ไป ตัวอย่างเช่น:
char *p = NULL;
*p;
เรายกเลิกการอ้างอิงตัวชี้ NULL โดยไม่ต้องเข้าถึงค่าของมัน หรือเราสามารถทำ:
p1 = &(*p);
sz = sizeof(*p);
อีกครั้งพิจารณา แต่ไม่เคยเข้าถึงค่า รหัสดังกล่าวจะไม่ผิดพลาด: เกิดข้อผิดพลาดเมื่อคุณเข้าถึงข้อมูลโดยตัวชี้ที่ไม่ถูกต้อง อย่างไรก็ตามโชคไม่ดีที่ตามมาตรฐานการยกเลิกการอ้างอิงตัวชี้ที่ไม่ถูกต้องนั้นเป็นพฤติกรรมที่ไม่ได้กำหนด (มีข้อยกเว้นเล็กน้อย) แม้ว่าคุณจะไม่ได้พยายามสัมผัสข้อมูลจริงก็ตาม
ดังนั้นในระยะสั้น: การยกเลิกการลงทะเบียนตัวชี้หมายถึงการใช้ตัวดำเนินการการยกเลิกการลงทะเบียนกับมัน ตัวดำเนินการนั้นเพียงส่งคืนค่า l สำหรับการใช้ในอนาคตของคุณ
*p;
ทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด แม้ว่าคุณจะถูกต้องที่ dereferencing ไม่สามารถเข้าถึงค่าต่อ seรหัส*p;
จะเข้าถึงค่า