เหตุใดคอมไพเลอร์ C และ C ++ จึงอนุญาตให้มีความยาวอาร์เรย์ในลายเซ็นของฟังก์ชันเมื่อไม่เคยบังคับใช้


133

นี่คือสิ่งที่ฉันพบในช่วงการเรียนรู้ของฉัน:

#include<iostream>
using namespace std;
int dis(char a[1])
{
    int length = strlen(a);
    char c = a[2];
    return length;
}
int main()
{
    char b[4] = "abc";
    int c = dis(b);
    cout << c;
    return 0;
}  

ดังนั้นในตัวแปรint dis(char a[1])ที่[1]ดูเหมือนว่าจะทำอะไรและไม่ทำงานทั้งหมดเพราะฉันสามารถใช้
a[2]เช่นเดียวกับint a[]หรือchar *a. ฉันรู้ว่าชื่ออาร์เรย์เป็นตัวชี้และวิธีการถ่ายทอดอาร์เรย์ดังนั้นปริศนาของฉันจึงไม่เกี่ยวกับส่วนนี้

สิ่งที่ฉันอยากรู้คือทำไมคอมไพเลอร์ถึงยอมให้มีพฤติกรรมนี้ ( int a[1]) หรือมันมีความหมายอื่นที่ฉันไม่รู้?


6
นั่นเป็นเพราะคุณไม่สามารถส่งอาร์เรย์ไปยังฟังก์ชันได้
Ed S.

37
ฉันคิดว่าคำถามที่นี่คือสาเหตุที่ C อนุญาตให้คุณประกาศพารามิเตอร์เป็นประเภทอาร์เรย์เมื่อมันจะทำงานเหมือนกับตัวชี้อย่างไรก็ตาม
Brian

8
@ ไบรอัน: ฉันไม่แน่ใจว่านี่เป็นอาร์กิวเมนต์สำหรับหรือต่อต้านพฤติกรรม แต่ก็ใช้ได้เช่นกันหากประเภทอาร์กิวเมนต์เป็นtypedefประเภทอาร์เรย์ ดังนั้น "การสลายตัวเป็นตัวชี้" ในประเภทอาร์กิวเมนต์ไม่ได้เป็นเพียงแค่น้ำตาลวากยสัมพันธ์ที่แทนที่[]ด้วย*แต่มันต้องผ่านระบบประเภทจริงๆ สิ่งนี้มีผลตามมาในโลกแห่งความเป็นจริงสำหรับบางประเภทมาตรฐานเช่นva_listนั้นอาจถูกกำหนดด้วยอาร์เรย์หรือประเภทที่ไม่ใช่อาร์เรย์
R .. GitHub STOP HELPING ICE

4
@songyuanyao คุณสามารถบรรลุสิ่งที่ไม่แตกต่างกันอย่างสิ้นเชิงใน C (และ C ++) int dis(char (*a)[1])โดยใช้ตัวชี้: dis(&b)จากนั้นคุณผ่านตัวชี้ไปยังอาร์เรย์: หากคุณยินดีที่จะใช้คุณสมบัติ C ที่ไม่มีใน C ++ คุณสามารถพูดสิ่งต่างๆเช่นvoid foo(int data[static 256])และint bar(double matrix[*][*])แต่นั่นคือเวิร์มอื่น ๆ ทั้งหมด
Stuart Olsen

1
@StuartOlsen ประเด็นไม่ได้อยู่ที่มาตรฐานกำหนดว่าอะไร ประเด็นคือทำไมใคร ๆ ก็นิยามมันแบบนั้น
user253751

คำตอบ:


157

มันเป็นมุมแหลมของไวยากรณ์สำหรับการส่งอาร์เรย์ไปยังฟังก์ชัน

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

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

การตัดสินใจอนุญาตให้ใช้ไวยากรณ์นี้เกิดขึ้นในปี 1970 และทำให้เกิดความสับสนมากมายนับตั้งแต่ ...


22
ในฐานะโปรแกรมเมอร์ที่ไม่ใช่ C ฉันพบว่าคำตอบนี้สามารถเข้าถึงได้มาก +1
asteri

22
+1 สำหรับ "การตัดสินใจอนุญาตให้ใช้ไวยากรณ์นี้เกิดขึ้นในปี 1970 และทำให้เกิดความสับสนมากมายนับตั้งแต่ ... "
NoSenseEtAl

8
นี่เป็นความจริง แต่ยังสามารถส่งอาร์เรย์ที่มีขนาดเท่านี้ได้โดยใช้void foo(int (*somearray)[20])ไวยากรณ์ ในกรณีนี้มีการบังคับใช้ 20 ในไซต์ผู้โทร
v.oddou

14
-1 ในฐานะโปรแกรมเมอร์ C ฉันพบว่าคำตอบนี้ไม่ถูกต้อง []จะไม่ถูกละเว้นในอาร์เรย์หลายมิติดังที่แสดงในคำตอบของ pat ดังนั้นการรวมไวยากรณ์อาร์เรย์จึงจำเป็น นอกจากนี้ไม่มีสิ่งใดหยุดคอมไพเลอร์ไม่ให้ออกคำเตือนแม้ในอาร์เรย์มิติเดียว
user694733

7
โดย "เนื้อหาใน [] ของคุณ" ฉันกำลังพูดถึงรหัสในคำถามโดยเฉพาะ มุมแหลมทางไวยากรณ์นี้ไม่จำเป็นเลยสิ่งเดียวกันนี้สามารถทำได้โดยใช้ไวยากรณ์ของตัวชี้กล่าวคือถ้าตัวชี้ถูกส่งผ่านไปแล้วต้องให้พารามิเตอร์เป็นตัวประกาศตัวชี้ เช่นในตัวอย่างของ pat void foo(int (*args)[20]);นอกจากนี้การพูดอย่างเคร่งครัด C ไม่มีอาร์เรย์หลายมิติ แต่มีอาร์เรย์ที่สามารถเป็นอาร์เรย์อื่นได้ สิ่งนี้ไม่ได้เปลี่ยนแปลงอะไร
MM

144

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

#include <stdio.h>

void foo(int args[10][20])
{
    printf("%zd\n", sizeof(args[0]));
}

int main(int argc, char **argv)
{
    int a[2][20];
    foo(a);
    return 0;
}

ขนาดของมิติแรก[10]จะถูกละเว้น คอมไพเลอร์จะไม่ป้องกันไม่ให้คุณสร้างดัชนีจากจุดสิ้นสุด (สังเกตว่าทางการต้องการ 10 องค์ประกอบ แต่ของจริงมีเพียง 2) อย่างไรก็ตามขนาดของมิติที่สอง[20]จะใช้เพื่อกำหนดระยะก้าวของแต่ละแถวและที่นี่ทางการต้องตรงกับของจริง อีกครั้งคอมไพลเลอร์จะไม่ป้องกันไม่ให้คุณสร้างดัชนีจากจุดสิ้นสุดของมิติที่สองเช่นกัน

ไบต์ออฟเซ็ตจากฐานของอาร์เรย์ไปยังองค์ประกอบargs[row][col]ถูกกำหนดโดย:

sizeof(int)*(col + 20*row)

โปรดทราบว่าถ้าเป็นcol >= 20เช่นนั้นคุณจะทำดัชนีในแถวถัดไป (หรือปิดท้ายอาร์เรย์ทั้งหมด)

sizeof(args[0])ผลตอบแทนในเครื่องของฉันที่80 sizeof(int) == 4อย่างไรก็ตามหากฉันพยายามรับsizeof(args)ฉันจะได้รับคำเตือนคอมไพเลอร์ดังต่อไปนี้:

foo.c:5:27: warning: sizeof on array function parameter will return size of 'int (*)[20]' instead of 'int [10][20]' [-Wsizeof-array-argument]
    printf("%zd\n", sizeof(args));
                          ^
foo.c:3:14: note: declared here
void foo(int args[10][20])
             ^
1 warning generated.

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


มีประโยชน์มาก - ความสอดคล้องกับสิ่งนี้ก็เป็นไปได้เช่นกันเนื่องจากเหตุผลของมุมแหลมในกรณี 1 มิติ
jwg

1
มันเป็นความคิดเดียวกับกรณี 1 มิติ สิ่งที่ดูเหมือนอาร์เรย์ 2 มิติใน C และ C ++ นั้นแท้จริงแล้วคืออาร์เรย์ 1 มิติซึ่งแต่ละองค์ประกอบเป็นอาร์เรย์ 1-D อื่น ในกรณีนี้เรามีอาร์เรย์ที่มี 10 องค์ประกอบซึ่งแต่ละองค์ประกอบคือ "array of 20 ints" ตามที่อธิบายไว้ในโพสต์ของฉันสิ่งที่ส่งผ่านไปยังฟังก์ชันคือตัวชี้ไปยังองค์ประกอบแรกของargs. ในกรณีนี้องค์ประกอบแรกของ args คือ "อาร์เรย์ 20 ints" พอยน์เตอร์ประกอบด้วยข้อมูลประเภท สิ่งที่ส่งผ่านคือ "ตัวชี้ไปยังอาร์เรย์ 20 ints"
ม.

9
ใช่นั่นคือสิ่งที่int (*)[20]ประเภทคือ; "ตัวชี้ไปยังอาร์เรย์ 20 ints"
pat

@pat คุณบอกว่าเราสามารถข้ามมิติแรกได้ แต่ไม่ใช่มิติอื่นแล้วทำไมโค้ดนี้ถึงทำงานโดยไม่มีข้อผิดพลาดหรือลิงค์ CODE เตือน: ide.geeksforgeeks.org/WMoKbsYhB8 โปรดอธิบาย ฉันพลาดอะไรไปรึเปล่า?
Vinay Yadav

ชนิดของint (*p)[]คือตัวชี้ไปยังอาร์เรย์ 1 มิติที่มีความยาวไม่แน่นอน ขนาดของ*pไม่ได้กำหนดดังนั้นคุณจึงไม่สามารถสร้างดัชนีpได้โดยตรง (แม้ว่าจะมีดัชนี0!) สิ่งเดียวที่คุณสามารถทำได้ด้วยpคือการ dereference ว่ามันเป็นแล้วเป็นดัชนี*p (*p)[i]สิ่งนี้ไม่ได้รักษาโครงสร้าง 2 มิติของอาร์เรย์เดิม
ลูบ

33

ปัญหาและวิธีเอาชนะใน C ++

ปัญหาที่เกิดขึ้นได้รับการอธิบายอย่างกว้างขวางโดยpatและแมตต์ โดยทั่วไปคอมไพเลอร์จะละเว้นมิติแรกของขนาดของอาร์เรย์อย่างมีประสิทธิภาพโดยไม่สนใจขนาดของอาร์กิวเมนต์ที่ส่งผ่าน

ในทางกลับกัน C ++ คุณสามารถเอาชนะข้อ จำกัด นี้ได้อย่างง่ายดายด้วยสองวิธี:

  • ใช้การอ้างอิง
  • โดยใช้std::array(ตั้งแต่ C ++ 11)

อ้างอิง

หากฟังก์ชันของคุณพยายามอ่านหรือแก้ไขอาร์เรย์ที่มีอยู่เท่านั้น (ไม่ได้คัดลอก) คุณสามารถใช้การอ้างอิงได้อย่างง่ายดาย

ตัวอย่างเช่นสมมติว่าคุณต้องการให้มีฟังก์ชั่นที่รีเซ็ตอาร์เรย์ของสิบต์ints 0ตั้งค่าองค์ประกอบทุก คุณสามารถทำได้อย่างง่ายดายโดยใช้ลายเซ็นฟังก์ชันต่อไปนี้:

void reset(int (&array)[10]) { ... }

ไม่เพียง แต่จะใช้งานได้ดีเท่านั้น แต่ยังบังคับใช้มิติของอาร์เรย์ด้วย

นอกจากนี้คุณยังสามารถใช้เทมเพลตเพื่อสร้างโค้ดด้านบนทั่วไปได้ :

template<class Type, std::size_t N>
void reset(Type (&array)[N]) { ... }

และสุดท้ายคุณสามารถใช้ประโยชน์จากconstความถูกต้อง ลองพิจารณาฟังก์ชันที่พิมพ์อาร์เรย์ 10 องค์ประกอบ:

void show(const int (&array)[10]) { ... }

โดยใช้constรอบคัดเลือกเราจะป้องกันไม่ให้มีการปรับเปลี่ยนไปได้


คลาสไลบรารีมาตรฐานสำหรับอาร์เรย์

หากคุณพิจารณาไวยากรณ์ข้างต้นทั้งน่าเกลียดและไม่จำเป็นอย่างที่ฉันทำเราสามารถโยนมันลงในกระป๋องและใช้std::arrayแทน (ตั้งแต่ C ++ 11)

นี่คือรหัส refactored:

void reset(std::array<int, 10>& array) { ... }
void show(std::array<int, 10> const& array) { ... }

มันวิเศษไม่ใช่เหรอ? ไม่ต้องพูดถึงว่าเคล็ดลับรหัสทั่วไปที่ฉันเคยสอนคุณไปก่อนหน้านี้ยังใช้งานได้:

template<class Type, std::size_t N>
void reset(std::array<Type, N>& array) { ... }

template<class Type, std::size_t N>
void show(const std::array<Type, N>& array) { ... }

ไม่เพียงแค่นั้น แต่คุณจะได้รับสำเนาและย้ายความหมายได้ฟรี :)

void copy(std::array<Type, N> array) {
    // a copy of the original passed array 
    // is made and can be dealt with indipendently
    // from the original
}

ดังนั้นสิ่งที่คุณรอ? ไปใช้std::array.


2
@kietz ขออภัยการแก้ไขที่คุณแนะนำถูกปฏิเสธ แต่เราถือว่า C ++ 11 กำลังใช้งานโดยอัตโนมัติเว้นแต่จะระบุไว้เป็นอย่างอื่น
รองเท้า

นี่เป็นเรื่องจริง แต่เราควรระบุด้วยว่าโซลูชันใดเป็น C ++ 11 เท่านั้นตามลิงค์ที่คุณให้มา
trlkly

@trlkly ฉันเห็นด้วย ฉันได้แก้ไขคำตอบตามนั้น ขอบคุณที่ชี้ให้เห็น
รองเท้า

9

เป็นคุณสมบัติที่น่าสนุกของCที่ช่วยให้คุณยิงเท้าตัวเองได้อย่างมีประสิทธิภาพหากคุณเอียงมาก

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

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


3
ใช่. C ได้รับการออกแบบมาเพื่อไว้วางใจโปรแกรมเมอร์มากกว่าคอมไพเลอร์ หากคุณจัดทำดัชนีจุดสิ้นสุดของอาร์เรย์อย่างโจ่งแจ้งคุณต้องทำสิ่งที่พิเศษและตั้งใจ
John

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

7

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

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

typedef struct {
  int a[10];
} myarray_t;

myarray_t my_function(myarray_t foo) {

  myarray_t bar;

  ...

  return bar;

}

คุณต้องเข้าถึงองค์ประกอบเช่นนี้: foo.a [1] ".a" พิเศษอาจดูแปลก ๆ แต่เคล็ดลับนี้ช่วยเพิ่มฟังก์ชันการทำงานที่ยอดเยี่ยมให้กับภาษา C


7
คุณกำลังสับสนในการตรวจสอบขอบเขตรันไทม์ด้วยการตรวจสอบประเภทเวลาคอมไพล์
Ben Voigt

@ Ben Voigt: ฉันกำลังพูดถึงการตรวจสอบขอบเขตเท่านั้นเหมือนคำถามเดิม
user34814

2
การตรวจสอบขอบเขตเวลาคอมไพล์ @ user34814 อยู่ในขอบเขตของการตรวจสอบประเภท ภาษาระดับสูงหลายภาษาเสนอคุณสมบัตินี้
Leushenko

5

เพื่อบอกคอมไพเลอร์ว่า myArray ชี้ไปที่อาร์เรย์อย่างน้อย 10 ints:

void bar(int myArray[static 10])

คอมไพเลอร์ที่ดีควรแจ้งเตือนคุณหากคุณเข้าถึง myArray [10] หากไม่มีคีย์เวิร์ด "คงที่" คำหลัก 10 คำจะไม่มีความหมายเลย


1
เหตุใดคอมไพเลอร์จึงควรเตือนหากคุณเข้าถึงองค์ประกอบที่ 11 และอาร์เรย์มีอย่างน้อย 10 องค์ประกอบ
nwellnhof

สันนิษฐานว่าเป็นเพราะคอมไพเลอร์บังคับได้ว่าคุณมีอย่างน้อย 10 องค์ประกอบเท่านั้น หากคุณพยายามเข้าถึงองค์ประกอบที่ 11 จะไม่สามารถมั่นใจได้ว่ามีอยู่จริง (แม้ว่าอาจจะ)
Dylan Watson

2
ฉันไม่คิดว่าเป็นการอ่านมาตรฐานที่ถูกต้อง [static]อนุญาตให้คอมไพเลอร์เตือนหากคุณเรียก barใช้int[5]ไฟล์. มันไม่ได้กำหนดสิ่งที่คุณอาจเข้าถึงภายใน barความรับผิดชอบอยู่ที่ด้านผู้โทรทั้งหมด
แท็บ

3
error: expected primary-expression before 'static'ไม่เคยเห็นไวยากรณ์นี้ นี่ไม่น่าจะเป็น C หรือ C ++ มาตรฐาน
v.oddou

3
@ v.oddou ระบุไว้ใน C99 ใน 6.7.5.2 และ 6.7.5.3
Samuel Edwin Ward

5

นี่คือ "คุณลักษณะ" ที่รู้จักกันดีของ C ซึ่งส่งต่อไปยัง C ++ เนื่องจาก C ++ ควรรวบรวมรหัส C อย่างถูกต้อง

ปัญหาเกิดจากหลายด้าน:

  1. ชื่ออาร์เรย์ควรจะเทียบเท่ากับตัวชี้
  2. C ควรจะเร็ว แต่เดิมผู้พัฒนาเป็น "แอสเซมเบลอร์ระดับสูง" ชนิดหนึ่ง (ออกแบบมาโดยเฉพาะเพื่อเขียน "ระบบปฏิบัติการพกพา" ตัวแรก: Unix) ดังนั้นจึงไม่ใช่ควรแทรกโค้ด "ซ่อน" การตรวจสอบช่วงรันไทม์จึงเป็น "สิ่งต้องห้าม"
  3. รหัสเครื่องที่สร้างขึ้นเพื่อเข้าถึงอาร์เรย์แบบคงที่หรือแบบไดนามิก (ไม่ว่าจะในสแต็กหรือจัดสรร) นั้นแตกต่างกันจริงๆ
  4. เนื่องจากฟังก์ชันที่เรียกว่าไม่สามารถทราบ "ชนิด" ของอาร์เรย์ที่ส่งผ่านไปได้เนื่องจากอาร์กิวเมนต์ทุกอย่างควรเป็นตัวชี้และถือว่าเป็นเช่นนั้น

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

อย่างไรก็ตามทั้งหมดข้างต้นไม่เป็นความจริงอีกต่อไป: p

ที่ทันสมัยที่สุด C / C ++ คอมไพเลอร์ทำขอบเขตการสนับสนุนการตรวจสอบ แต่มาตรฐานต้องมันจะปิดโดยปริยาย (สำหรับความเข้ากันได้ย้อนหลัง) gcc เวอร์ชันล่าสุดอย่างสมเหตุสมผลเช่นทำการตรวจสอบช่วงเวลาคอมไพล์ด้วย "-O3 -Wall -Wextra" และการตรวจสอบขอบเขตรันไทม์แบบเต็มด้วย "-fbounds-checked"


บางที C ++ ถูกควรจะรวบรวมรหัส C 20 ปีที่ผ่านมา แต่ก็แน่นอนคือไม่ได้และไม่ได้เป็นเวลานาน (C ++ 98? C99 อย่างน้อยที่ไม่ได้รับ "คงที่" โดยใด ๆ C ++ ใหม่มาตรฐาน)
hyde

@hyde นั่นฟังดูรุนแรงเกินไปสำหรับฉัน หากต้องการอ้างอิง Stroustrup "ด้วยข้อยกเว้นเล็กน้อย C คือส่วนย่อยของ C ++" (C ++ PL 4th ed., sec. 1.2.1) ในขณะที่ทั้ง C ++ และ C พัฒนาไปไกลขึ้นและคุณลักษณะจากเวอร์ชัน C ล่าสุดมีอยู่ซึ่งไม่มีในเวอร์ชัน C ++ ล่าสุดโดยรวมแล้วฉันคิดว่าใบเสนอราคาของ Stroustrup ยังคงใช้ได้
mvw

@mvw โค้ด C ส่วนใหญ่ที่เขียนในพันปีนี้ซึ่งไม่ได้ตั้งใจให้ C ++ เข้ากันได้โดยการหลีกเลี่ยงคุณสมบัติที่เข้ากันไม่ได้จะใช้ไวยากรณ์ตัวเริ่มต้นที่กำหนดโดย C99 ( struct MyStruct s = { .field1 = 1, .field2 = 2 };) สำหรับการเตรียมใช้งานโครงสร้างเนื่องจากเป็นวิธีที่ชัดเจนกว่ามากในการเริ่มต้นโครงสร้าง ด้วยเหตุนี้โค้ด C ปัจจุบันส่วนใหญ่จะถูกปฏิเสธโดยคอมไพเลอร์ C ++ มาตรฐานเนื่องจากโค้ด C ส่วนใหญ่จะเริ่มต้นโครงสร้าง
hyde

@mvw อาจกล่าวได้ว่า C ++ ควรจะเข้ากันได้กับ C ดังนั้นจึงเป็นไปได้ที่จะเขียนโค้ดซึ่งจะรวบรวมกับคอมไพเลอร์ C และ C ++ หากมีการประนีประนอมบางอย่าง แต่ต้องใช้ชุดย่อยของทั้ง C และ C ++ ไม่ใช่แค่ชุดย่อยของ C ++
hyde

@hyde คุณจะแปลกใจว่าโค้ด C สามารถคอมไพล์ C ++ ได้มากแค่ไหน ไม่กี่ปีที่ผ่านมาเคอร์เนล Linux ทั้งหมดสามารถคอมไพล์ C ++ ได้ (ฉันไม่รู้ว่ามันยังคงเป็นจริงอยู่หรือเปล่า) ฉันคอมไพล์โค้ด C ในคอมไพเลอร์ C ++ เป็นประจำเพื่อรับการตรวจสอบคำเตือนที่เหนือกว่ามีเพียง "การผลิต" เท่านั้นที่รวบรวมในโหมด C เพื่อบีบการปรับให้เหมาะสมที่สุด
ZioByte

3

C จะไม่เพียง แต่แปลงพารามิเตอร์ประเภทint[5]เป็น*int; ได้รับการประกาศtypedef int intArray5[5];ก็จะเปลี่ยนพารามิเตอร์ของประเภทintArray5จะ*intได้เป็นอย่างดี มีบางสถานการณ์ที่พฤติกรรมนี้แม้จะแปลก แต่ก็มีประโยชน์ (โดยเฉพาะอย่างยิ่งกับสิ่งต่างๆเช่นที่va_listกำหนดไว้stdargs.hซึ่งการใช้งานบางอย่างกำหนดเป็นอาร์เรย์) การอนุญาตให้เป็นประเภทพารามิเตอร์ที่กำหนดเป็นint[5](การละเว้นมิติข้อมูล) เป็นเรื่องที่สมเหตุสมผลแต่ไม่อนุญาตint[5]ระบุโดยตรง

ฉันพบว่าการจัดการพารามิเตอร์ประเภทอาร์เรย์ของ C เป็นเรื่องไร้สาระ แต่เป็นผลมาจากความพยายามที่จะใช้ภาษาเฉพาะกิจซึ่งส่วนใหญ่ไม่ได้มีการกำหนดหรือคิดออกมาเป็นอย่างดีโดยเฉพาะและพยายามหาพฤติกรรม ข้อกำหนดที่สอดคล้องกับการใช้งานที่มีอยู่สำหรับโปรแกรมที่มีอยู่ นิสัยใจคอของ C หลายอย่างมีความหมายเมื่อมองในแง่นั้นโดยเฉพาะอย่างยิ่งถ้ามีคนคิดว่าเมื่อหลายคนถูกประดิษฐ์ขึ้นส่วนใหญ่ของภาษาที่เรารู้จักในปัจจุบันยังไม่มีอยู่จริง จากสิ่งที่ฉันเข้าใจในรุ่นก่อนถึง C เรียกว่า BCPL คอมไพเลอร์ไม่ได้ติดตามประเภทตัวแปรได้ดีนัก การประกาศint arr[5];เทียบเท่ากับint anonymousAllocation[5],*arr = anonymousAllocation;; เมื่อจัดสรรการจัดสรรแล้ว คอมไพเลอร์ไม่รู้หรือไม่สนใจว่าarrเป็นตัวชี้หรืออาร์เรย์ เมื่อเข้าถึงเป็นอย่างใดอย่างหนึ่งarr[x]หรือ*arrจะถือว่าเป็นตัวชี้ไม่ว่าจะประกาศด้วยวิธีใดก็ตาม


1

สิ่งหนึ่งที่ยังไม่ได้รับคำตอบคือคำถามที่แท้จริง

คำตอบที่ให้ไปแล้วอธิบายว่าอาร์เรย์ไม่สามารถส่งผ่านค่าไปยังฟังก์ชันใน C หรือ C ++ นอกจากนี้ยังอธิบายด้วยว่าพารามิเตอร์ที่ประกาศเป็นint[]จะถือว่าเป็นประเภทint *และตัวแปรประเภทint[]สามารถส่งผ่านไปยังฟังก์ชันดังกล่าวได้

แต่พวกเขาไม่ได้อธิบายว่าเหตุใดจึงไม่เคยเกิดข้อผิดพลาดในการระบุความยาวอาร์เรย์อย่างชัดเจน

void f(int *); // makes perfect sense
void f(int []); // sort of makes sense
void f(int [10]); // makes no sense

เหตุใดสุดท้ายจึงไม่เกิดข้อผิดพลาด

เหตุผลก็คือมันทำให้เกิดปัญหากับ typedefs

typedef int myarray[10];
void f(myarray array);

หากเป็นข้อผิดพลาดในการระบุความยาวอาร์เรย์ในพารามิเตอร์ฟังก์ชันคุณจะไม่สามารถใช้myarrayชื่อในพารามิเตอร์ฟังก์ชันได้ และเนื่องจากการใช้งานบางอย่างใช้ประเภทอาร์เรย์สำหรับประเภทไลบรารีมาตรฐานเช่นva_listและการใช้งานทั้งหมดจำเป็นต้องสร้างjmp_bufประเภทอาร์เรย์จึงเป็นปัญหามากหากไม่มีวิธีมาตรฐานในการประกาศพารามิเตอร์ฟังก์ชันโดยใช้ชื่อเหล่านั้น: หากไม่มีความสามารถดังกล่าวก็สามารถทำได้ ไม่ใช่การนำฟังก์ชันแบบพกพาเช่นvprintf.


0

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

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