เหตุใดขนาดของพารามิเตอร์อาร์เรย์จึงไม่เหมือนกับภายใน main


104

เหตุใดขนาดของอาร์เรย์ที่ส่งเป็นพารามิเตอร์จึงไม่เหมือนกับภายใน main

#include <stdio.h>

void PrintSize(int p_someArray[10]);

int main () {
    int myArray[10];
    printf("%d\n", sizeof(myArray)); /* As expected, 40 */
    PrintSize(myArray);/* Prints 4, not 40 */
}

void PrintSize(int p_someArray[10]){
    printf("%d\n", sizeof(p_someArray));
}

คำตอบ:


103

ประเภทอาร์เรย์จะถูกแปลงเป็นประเภทตัวชี้โดยปริยายเมื่อคุณส่งผ่านไปยังฟังก์ชัน

ดังนั้น,

void PrintSize(int p_someArray[10]) {
    printf("%zu\n", sizeof(p_someArray));
}

และ

void PrintSize(int *p_someArray) {
    printf("%zu\n", sizeof(p_someArray));
}

เทียบเท่า ดังนั้นสิ่งที่คุณจะได้รับคือมูลค่าของsizeof(int*)


1
ใน C ++ คุณสามารถส่งผ่านอาร์เรย์โดยอ้างอิงถึงฟังก์ชัน แต่คุณไม่สามารถทำได้ใน C.
Prasoon Saurav

13
คุณจะต้องส่งขนาดของอาร์เรย์เป็นพารามิเตอร์แยกต่างหาก จากนั้นขนาดของอาร์เรย์จะเป็น sizeof (* p_someArray) * length
Aric TenEyck

13
เล็กน้อย nit: sizeofโอเปอเรเตอร์ส่งคืนอ็อบเจ็กต์ประเภทsize_tดังนั้นคุณควรพิมพ์ด้วย%zu(C99) หรือส่งไปintหากคุณใช้สิ่งที่%dต้องการข้างต้นในการprintfโทรของคุณ
Alok Singhal

4
คำกล่าวของ Alok ถูกต้อง การใช้ตัวระบุรูปแบบที่ไม่ถูกต้องใน printf (.. ) คือ UB
Prasoon Saurav

1
@ Chris_45: C ไม่มีการอ้างอิง แต่ใน C void PrintSize(int (*p_someArray)[10])คุณสามารถส่งผ่านอาร์เรย์โดยชี้ไปยังอาร์เรย์ทั้งหมดเช่น: ภายในฟังก์ชั่นที่คุณสามารถเข้าถึงอาร์เรย์โดยใช้ประกอบการ dereference :* sizeof(*p_someArray)สิ่งนี้จะมีผลเช่นเดียวกับการใช้การอ้างอิงใน C ++
AnT

18

มันเป็นตัวชี้นั่นคือเหตุผลที่การใช้งานทั่วไปในการส่งขนาดของอาร์เรย์เป็นพารามิเตอร์ที่สองไปยังฟังก์ชัน


16

ตามที่คนอื่น ๆ ระบุไว้อาร์เรย์จะสลายตัวเพื่อชี้ไปยังองค์ประกอบแรกเมื่อใช้เป็นพารามิเตอร์ฟังก์ชัน นอกจากนี้ยังควรสังเกตด้วยว่า sizeof ไม่ได้ประเมินนิพจน์และไม่ต้องใช้วงเล็บเมื่อใช้กับนิพจน์ดังนั้นพารามิเตอร์ของคุณจึงไม่ได้ถูกใช้เลยดังนั้นคุณอาจเขียน sizeof ด้วย type แทนที่จะเป็นค่าก็ได้

#include <stdio.h>

void PrintSize1 ( int someArray[][10] );
void PrintSize2 ( int someArray[10] );

int main ()
{
    int myArray[10];
    printf ( "%d\n", sizeof myArray ); /* as expected 40 */
    printf ( "%d\n", sizeof ( int[10] ) ); /* requires parens */
    PrintSize1 ( 0 ); /* prints 40, does not evaluate 0[0] */
    PrintSize2 ( 0 ); /* prints 40, someArray unused */
}

void PrintSize1 ( int someArray[][10] )
{
    printf ( "%d\n", sizeof someArray[0] );
}

void PrintSize2 ( int someArray[10] )
{
    printf ( "%d\n", sizeof ( int[10] ) );
}

12

ดังนั้นคุณจะต้องส่งผ่านความยาวของอาร์เรย์เป็นพารามิเตอร์ที่สอง เมื่อคุณเขียนโค้ดซึ่งคุณทั้งคู่ประกาศอาร์เรย์ที่มีขนาดคงที่และส่งอาร์เรย์นั้นไปยังฟังก์ชันในภายหลังจึงเป็นเรื่องยากที่จะให้ค่าคงที่ความยาวอาร์เรย์แสดงขึ้นหลายตำแหน่งในโค้ดของคุณ ...

K&R เพื่อช่วยเหลือ:

#define N_ELEMENTS(array) (sizeof(array)/sizeof((array)[0])) 

ตอนนี้คุณสามารถทำได้เช่น:

int a[10];
...
myfunction(a, N_ELEMENTS(a));

จะเกิดอะไรขึ้นถ้าขนาดของอาร์เรย์ไม่พร้อมใช้งานในขณะเข้ารหัส แต่จะใช้ได้เฉพาะในขณะรัน มีวิธีอื่นในการคำนวณขนาดของอาร์เรย์โดยไม่ต้องเข้ารหัสขนาดหรือไม่
weefwefwqg3

วิธีการที่แสดงใช้ได้ผลก็ต่อเมื่อการประกาศอาร์เรย์เป็น "ในมุมมอง" สำหรับกรณีอื่น ๆ ทั้งหมดคุณต้องส่งผ่านความยาวอาร์เรย์ด้วยตนเอง
SC Madsen

5

เนื่องจากอาร์เรย์จะสลายตัวเป็นพอยน์เตอร์เมื่อส่งผ่านเป็นพารามิเตอร์ นี่คือวิธีการทำงานของ C แม้ว่าคุณจะสามารถส่ง "อาร์เรย์" ใน C ++ โดยการอ้างอิงและเอาชนะปัญหานี้ได้ โปรดทราบว่าคุณสามารถส่งอาร์เรย์ที่มีขนาดแตกต่างกันไปยังฟังก์ชันนี้ได้:

 // 10 is superfluous here! You can pass an array of different size!
void PrintSize(int p_someArray[10]);

5

พฤติกรรมที่คุณพบนั้นแท้จริงแล้วเป็นหูดขนาดใหญ่ในภาษาซี เมื่อใดก็ตามที่คุณประกาศฟังก์ชันที่รับพารามิเตอร์อาร์เรย์คอมไพลเลอร์จะไม่สนใจคุณและเปลี่ยนพารามิเตอร์เป็นตัวชี้ ดังนั้นการประกาศเหล่านี้จึงมีพฤติกรรมเหมือนข้อแรก:

void func(int *a)
void func(int a[])
void func(int a
typedef int array_plz[5];
void func(array_plz a)

a จะเป็นตัวชี้ไปที่ int ในทั้งสี่กรณี หากคุณส่งอาร์เรย์ไปยัง func มันจะสลายตัวเป็นตัวชี้ไปยังองค์ประกอบแรกทันที (ในระบบ 64 บิตตัวชี้ 64 บิตมีขนาดใหญ่เป็นสองเท่าของ int 32 บิตดังนั้นอัตราส่วนขนาดของคุณจะส่งกลับ 2)

จุดประสงค์เดียวของกฎนี้คือการรักษาความเข้ากันได้ย้อนหลังกับคอมไพเลอร์ในอดีตที่ไม่สนับสนุนการส่งผ่านค่าการรวมเป็นอาร์กิวเมนต์ของฟังก์ชัน

นี่ไม่ได้หมายความว่าจะไม่สามารถส่งอาร์เรย์ไปยังฟังก์ชันได้ คุณสามารถหลีกเลี่ยงหูดนี้ได้โดยการฝังอาร์เรย์ลงในโครงสร้าง (นี่คือจุดประสงค์ของ std :: array ของ C ++ 11):

struct array_rly {
int a[5];
};
void func(struct array_rly a)
{
printf("%zd\n", sizeof(a.a)/sizeof(a.a[0]));  /* prints 5 */
}

หรือโดยการส่งตัวชี้ไปยังอาร์เรย์:

void func(const int (*a)[5])
{
printf("%zd\n", sizeof(*a)/sizeof((*a)[0]));  /* prints 5 */
}

ในกรณีที่ขนาดอาร์เรย์ไม่ใช่ค่าคงที่เวลาคอมไพล์คุณสามารถใช้เทคนิคตัวชี้ไปยังอาร์เรย์กับอาร์เรย์ที่มีความยาวตัวแปร C99:

void func(int n, const int (*a)[n])
{
printf("%zd\n", sizeof(*a)/sizeof((*a)[0]));  /* prints n */
}

4

ใน c ++ คุณสามารถส่งอาร์เรย์โดยการอ้างอิงเพื่อจุดประสงค์นี้:

void foo(int (&array)[10])
{
    std::cout << sizeof(array) << "\n";
}

1
คำถามนี้จะช่วยคำถาม C ได้อย่างไร?
alk

3

ในภาษา C ไม่มีวิธีใดในการกำหนดขนาดของอาร์เรย์ที่ไม่รู้จักดังนั้นจึงต้องมีการส่งผ่านปริมาณเช่นเดียวกับตัวชี้ไปยังองค์ประกอบแรก


2
โดยทั่วไปคุณควรส่งขนาด (จำนวนองค์ประกอบ) ของอาร์เรย์พร้อมกับอาร์เรย์ไปยังฟังก์ชันเว้นแต่คุณจะมีวิธีอื่นในการกำหนดขนาดของมัน (เช่นเทอร์มิเนเตอร์อักขระว่างที่ส่วนท้ายของchar[]อาร์เรย์สตริง)
David R Tribble

ได้โปรด " อาร์เรย์ที่ไม่รู้จัก " คืออะไร?
alk

3

คุณไม่สามารถส่งอาร์เรย์ไปยังฟังก์ชันได้

หากคุณต้องการพิมพ์ขนาดจริง ๆ คุณสามารถส่งตัวชี้ไปยังอาร์เรย์ได้ แต่จะไม่เป็นแบบทั่วไปเลยเนื่องจากคุณต้องกำหนดขนาดอาร์เรย์สำหรับฟังก์ชันด้วย

#include <stdio.h>

void PrintSize(int (*p_anArray)[10]);

int main(void) {
    int myArray[10];
    printf("%d\n", sizeof(myArray)); /* as expected 40 */
    PrintSize(&myArray);/* prints 40 */
}

void PrintSize(int (*p_anArray)[10]){
    printf("%d\n", (int) sizeof(*p_anArray));
}

0

พฤติกรรมเป็นไปตามการออกแบบ

ไวยากรณ์เดียวกันในการประกาศพารามิเตอร์ฟังก์ชันหมายถึงสิ่งที่แตกต่างอย่างสิ้นเชิงกับการกำหนดตัวแปรโลคัล

มีการอธิบายเหตุผลไว้ในคำตอบอื่น ๆ


0

ในภาษา C เมื่อคุณส่งอาร์เรย์เป็นอาร์กิวเมนต์ไปยังฟังก์ชันจะถูกแปลงเป็นตัวชี้โดยอัตโนมัติอาร์เรย์ที่ส่งผ่านจากฟังก์ชันหนึ่งฟังก์ชันอื่น ๆ จะเรียกว่าการเรียกโดยการอ้างอิง นั่นคือเหตุผลที่ฟังก์ชันที่เรียกว่าได้รับเฉพาะตัวชี้ที่ชี้ไปยังองค์ประกอบแรกของฟังก์ชันนี่คือเหตุผล

fun (int a []) คล้ายกับ fun (int * a);

ดังนั้นเมื่อคุณพิมพ์ขนาดของอาร์เรย์ก็จะพิมพ์ขนาดขององค์ประกอบแรก


ในภาษา C ไม่มี "การเรียกโดยการอ้างอิง "
alk

" เมื่อคุณพิมพ์ขนาดของอาร์เรย์มันจะพิมพ์ขนาดขององค์ประกอบแรก " ไม่มันจะพิมพ์ขนาดของตัวชี้
alk

-1

ในการเขียนโปรแกรม "C" "sizeof ()" คือตัวดำเนินการและส่งกลับขนาดของวัตถุเป็นไบต์อาร์กิวเมนต์ของตัวดำเนินการ 'sizeof ()' ต้องเป็นประเภทค่าทางซ้าย (จำนวนเต็ม, จำนวนลอย, โครงสร้าง, อาร์เรย์ ) ดังนั้นหากคุณต้องการทราบขนาดของอาร์เรย์เป็นไบต์คุณสามารถทำได้ง่ายมากเพียงใช้ตัวดำเนินการ 'sizeof ()' และสำหรับอาร์กิวเมนต์ของเขาให้ใช้ชื่ออาร์เรย์ตัวอย่างเช่น:

#include <stdio.h>

main(){

 int n[10];
 printf("Size of n is: %d \n", sizeof(n)); 

}

เอาต์พุตในระบบ 32 บิตจะเป็น: ขนาดของ n คือ: 40 เนื่องจาก ineteger ในระบบ 32 คือ 4 ไบต์บน 64x เป็น 8 ไบต์ในกรณีนี้เรามีจำนวนเต็ม 10 ตัวที่ประกาศในอาร์เรย์เดียวดังนั้นผลลัพธ์คือ '10 * sizeof ( int) '.

เคล็ดลับบางประการ:

ถ้าเรามีอาร์เรย์ที่ประกาศเช่นนี้ 'int n [] = {1, 2, 3, ... 155 .. };' เราจึงอยากทราบว่าอาร์เรย์นี้เก็บองค์ประกอบไว้กี่องค์ประกอบ ใช้ alghorithm นี้:

ขนาดของ (name_of_the_array) / sizeof (array_type)

รหัส: #include

หลัก(){

int n[] = { 1, 2, 3, 44, 6, 7 };
printf("Number of elements: %d \n", sizeof(n) / sizeof(int));
return 0;

}


2
ยินดีต้อนรับสู่ StackOverflow และขอขอบคุณที่เขียนคำตอบ น่าเสียดายที่นี่ไม่ได้ตอบคำถามซึ่งเฉพาะเจาะจงเกี่ยวกับความแตกต่างระหว่างsizeof(n)สำหรับตัวแปรโลคัลและsizeof(arg)สำหรับอาร์กิวเมนต์ของฟังก์ชันแม้ว่าทั้งสองจะดูเหมือนเป็นประเภทint[10]ก็ตาม
MicroVirus

-3

อาร์เรย์มีขนาดหลวมเท่านั้น ส่วนใหญ่อาร์เรย์เป็นตัวชี้ไปยังหน่วยความจำ ขนาดในการประกาศของคุณจะบอกให้คอมไพเลอร์ทราบว่าจะจัดสรรหน่วยความจำสำหรับอาร์เรย์เท่าใด - ไม่เกี่ยวข้องกับประเภทดังนั้น sizeof () จึงไม่มีอะไรเกิดขึ้น


3
ขออภัยคำตอบนี้ทำให้เข้าใจผิด อาร์เรย์ไม่มี "ขนาดหลวม" หรือ "ชี้ไปที่หน่วยความจำ" อาร์เรย์มีขนาดที่แน่นอนมากและตำแหน่งที่ชื่ออาร์เรย์ย่อมาจากตัวชี้ไปยังองค์ประกอบแรกถูกระบุอย่างแม่นยำโดยมาตรฐาน C
Jens
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.