การเรียกใช้ฟังก์ชันที่มีตัวชี้ไปยังไม่ใช่ const และตัวชี้ไปยังอาร์กิวเมนต์ const ของที่อยู่เดียวกัน


14

ฉันต้องการเขียนฟังก์ชั่นที่ป้อนอาร์เรย์ของข้อมูลและส่งออกอาเรย์อีกหนึ่งข้อมูลโดยใช้พอยน์เตอร์

ฉันสงสัยว่าผลลัพธ์คืออะไรถ้าทั้งคู่srcและdstชี้ไปยังที่อยู่เดียวกันเพราะฉันรู้ว่าคอมไพเลอร์สามารถปรับให้เหมาะสมสำหรับ const มันเป็นพฤติกรรมที่ไม่ได้กำหนดหรือไม่? (ฉันติดแท็กทั้ง C และ C ++ เพราะฉันไม่แน่ใจว่าคำตอบอาจแตกต่างกันหรือไม่และฉันต้องการทราบเกี่ยวกับทั้งคู่)

void f(const char *src, char *dst) {
    dst[2] = src[0];
    dst[1] = src[1];
    dst[0] = src[2];
}

int main() {
    char s[] = "123";
    f(s,s);
    printf("%s\n", s);
    return 0;
}

นอกเหนือจากคำถามข้างต้นแล้วสิ่งนี้ได้กำหนดไว้อย่างชัดเจนหรือไม่หากฉันลบconstในรหัสต้นฉบับ?

คำตอบ:


17

ในขณะที่มันเป็นความจริงที่พฤติกรรมที่มีการกำหนดไว้อย่างดี - มันไม่เป็นความจริงที่คอมไพเลอร์สามารถ "เพิ่มประสิทธิภาพสำหรับ const" ในแง่ที่คุณหมายถึง

นั่นคือคอมไพเลอร์ไม่ได้รับอนุญาตสมมติว่าเพียงเพราะพารามิเตอร์เป็นconst T* ptrหน่วยความจำที่ชี้ไปตามptrจะไม่ถูกเปลี่ยนผ่านตัวชี้อื่น พอยน์เตอร์ไม่จำเป็นต้องเท่ากัน constเป็นภาระหน้าที่ไม่รับประกัน - ภาระผูกพันโดยคุณ (= ฟังก์ชั่น) จะไม่ทำการเปลี่ยนแปลงผ่านตัวชี้ว่า

เพื่อให้มีการรับประกันจริงนั้นคุณต้องทำเครื่องหมายตัวชี้ด้วยrestrictคำสำคัญ ดังนั้นถ้าคุณรวบรวมทั้งสองฟังก์ชั่น:

int foo(const int* x, int* y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

int bar(const int* x, int* restrict y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

foo()ฟังก์ชั่นจะต้องอ่านสองครั้งจากxในขณะที่bar()เพียงต้องการที่จะอ่านมันครั้งเดียว:

foo:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, DWORD PTR [rdi]  # second read
        ret
bar:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, eax              # no second read
        ret

ดูรายการสดGodBoltนี้

restrictเป็นเพียงคำหลักใน C (ตั้งแต่ C99) โชคไม่ดีที่มันยังไม่ได้ถูกนำมาใช้ใน C ++ จนถึงขณะนี้ (ด้วยเหตุผลที่ไม่ดีที่มีความซับซ้อนมากกว่าในการแนะนำใน C ++) คอมไพเลอร์หลายคนทำครับ-สนับสนุนมัน __restrictแต่เป็น

Bottom line: คอมไพเลอร์จะต้องรองรับกรณีการใช้ "ความลับ" ของคุณเมื่อรวบรวมf()และจะไม่มีปัญหาใด ๆ


ดูโพสต์นี้restrictเกี่ยวกับกรณีการใช้งานสำหรับ


constไม่ใช่“ ข้อผูกพันของคุณ (= ฟังก์ชั่น) ที่จะไม่ทำการเปลี่ยนแปลงผ่านตัวชี้นั้น” มาตรฐาน C อนุญาตให้ฟังก์ชันลบออกconstจากการร่ายแล้วปรับเปลี่ยนวัตถุผ่านผลลัพธ์ เป็นหลักconstเป็นเพียงคำแนะนำและความสะดวกสบายให้โปรแกรมเมอร์เพื่อช่วยหลีกเลี่ยงการแก้ไขวัตถุโดยไม่ได้ตั้งใจ
Eric Postpischil

@EricPostpischil: มันเป็นข้อผูกมัดที่คุณจะได้รับ
einpoklum

ภาระผูกพันที่คุณสามารถทำได้ไม่ใช่พันธะ
Eric Postpischil

2
@EricPostpischil: 1. คุณแยกเส้นขนที่นี่ 2. นั่นไม่เป็นความจริง
einpoklum

1
นี่คือสาเหตุmemcpyและstrcpyถูกประกาศด้วยrestrictอาร์กิวเมนต์ในขณะที่memmoveไม่ใช่ - เฉพาะหลังเท่านั้นที่อนุญาตให้ทับซ้อนกันระหว่างบล็อกหน่วยความจำ
Barmar

5

นี่ถูกกำหนดอย่างดี (ใน C ++, ไม่แน่ใจใน C อีกต่อไป), โดยมีและไม่มีตัวconstระบุ

สิ่งแรกที่จะมองหาเป็นกฎที่เข้มงวด aliasing 1 ถ้าsrcและdstชี้ไปที่วัตถุเดียวกัน:

เกี่ยวกับการconstคัดเลือกคุณอาจจะเถียงว่าตั้งแต่เมื่อdst == srcการทำงานของคุณได้อย่างมีประสิทธิภาพปรับเปลี่ยนสิ่งที่srcจุดไปไม่ควรจะมีคุณสมบัติเป็นsrc constนี่ไม่ใช่วิธีการconstทำงาน สองกรณีจำเป็นต้องได้รับการพิจารณา:

  1. เมื่อวัตถุถูกกำหนดให้constเป็นในขณะที่char const data[42];การปรับเปลี่ยนมัน (โดยตรงหรือโดยอ้อม) นำไปสู่พฤติกรรมที่ไม่ได้กำหนด
  2. เมื่อมีการconstกำหนดการอ้างอิงหรือตัวชี้ไปยังวัตถุเช่นเดียวกับchar const* pdata = data;หนึ่งสามารถปรับเปลี่ยนวัตถุพื้นฐานโดยที่มันไม่ได้ถูกกำหนดเป็นconst2 (ดู 1) ดังนั้นต่อไปนี้เป็นคำนิยามที่ดี:
int main()
{
    int result = 42;
    int const* presult = &result;
    *const_cast<int*>(presult) = 0;
    return *presult; // 0
}

1) กฎนามแฝงที่เข้มงวดคืออะไร
2) คือconst_castความปลอดภัยหรือไม่


บางที OP หมายถึงการจัดเรียงการมอบหมายที่เป็นไปได้หรือไม่?
Igor R.

char*และchar const*เข้ากันไม่ได้ _Generic((char *) 0, const char *: 1, default: 0))ประเมินเป็นศูนย์
Eric Postpischil

การใช้ถ้อยคำ“ เมื่อมีการconstกำหนดการอ้างอิงหรือตัวชี้ไปยังวัตถุ” ไม่ถูกต้อง คุณหมายถึงว่าเมื่อมีการconstกำหนดการอ้างอิงหรือตัวชี้ไปยังชนิดที่มีคุณสมบัติซึ่งไม่ได้หมายความว่าวัตถุนั้นถูกตั้งค่าให้ชี้ไปที่อาจไม่สามารถแก้ไขได้ (ด้วยวิธีการต่างๆ) (หากตัวชี้ชี้ไปที่constวัตถุนั่นหมายความว่าวัตถุนั้นเป็นไปconstตามนิยามแน่นอนดังนั้นพฤติกรรมของการพยายามแก้ไขไม่ได้กำหนดไว้)
Eric Postpischil

@ Eric ผมเพียง language-lawyerแต่ที่เฉพาะเจาะจงเมื่อคำถามเป็นเรื่องเกี่ยวกับมาตรฐานหรือแท็ก ความแน่นอนคือคุณค่าที่ฉันยึดมั่น แต่ฉันก็ตระหนักได้ว่ามันมีความซับซ้อนมากขึ้น ที่นี่ฉันตัดสินใจที่จะไปเพื่อความเรียบง่ายและประโยคที่เข้าใจง่ายเพราะฉันเชื่อว่านี่เป็นสิ่งที่ OP ต้องการ หากคุณคิดว่าเป็นอย่างอื่นโปรดตอบฉันจะอยู่ในหมู่คนแรกที่จะโหวตมัน อย่างไรก็ตามขอขอบคุณสำหรับความคิดเห็นของคุณ
YSC

3

สิ่งนี้ได้รับการกำหนดไว้อย่างดีใน C. กฎการใช้นามแฝงที่เข้มงวดไม่สามารถใช้ได้กับcharประเภทหรือกับตัวชี้สองประเภทเดียวกัน

ฉันไม่แน่ใจว่าคุณหมายถึงอะไรโดย "เพิ่มประสิทธิภาพสำหรับconst" คอมไพเลอร์ของฉัน (GCC 8.3.0 x86-64) สร้างรหัสเดียวกันแน่นอนสำหรับทั้งสองกรณี หากคุณเพิ่มตัวrestrictระบุไปยังพอยน์เตอร์แล้วรหัสที่สร้างขึ้นจะดีขึ้นเล็กน้อย แต่นั่นจะไม่ทำงานสำหรับกรณีของคุณพอยน์เตอร์จะเหมือนกัน

(C11 §6.5 7)

วัตถุต้องมีค่าที่เก็บไว้ที่เข้าถึงได้โดยนิพจน์ lvalue ที่มีประเภทใดประเภทหนึ่งต่อไปนี้:
- ชนิดที่เข้ากันได้กับชนิดของวัตถุที่มีประสิทธิภาพ
- รุ่นที่ผ่านการรับรองของชนิดที่เข้ากันได้กับชนิดประสิทธิผลของวัตถุ
- ประเภทที่เป็นประเภทที่ลงนามหรือไม่ได้ลงนามที่สอดคล้องกับประเภทของวัตถุที่มีประสิทธิภาพ
- ประเภทที่เป็นประเภทที่ลงนามหรือไม่ได้ลงนามที่สอดคล้องกับรุ่นที่มีประสิทธิภาพของประเภทของวัตถุ
- ประเภทรวมหรือสหภาพที่มี ประเภทดังกล่าวในหมู่สมาชิกของมัน (รวมถึง recursively, สมาชิกของ subaggregate หรือสหภาพที่มีอยู่) หรือ
- ประเภทตัวอักษร

ในกรณีนี้ (ไม่รวมrestrict) คุณจะได้รับ121ผลลัพธ์เสมอ

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