2010-05-29 33 views
15

Câu hỏi này xuất phát từ kết quả của một số chương trình ngôn ngữ hỗn hợp. Tôi đã có một thói quen Fortran tôi muốn gọi từ mã C++. Fortran vượt qua tất cả các thông số của nó bằng cách tham chiếu (trừ khi bạn nói nó khác).Trình biên dịch C++ thực sự chuyển tham số tham chiếu như thế nào?

Vì vậy, tôi nghĩ rằng tôi muốn được thông minh (khởi đầu tồi tệ ngay tại đó) trong C của tôi ++ mã và xác định một cái gì đó thường xuyên Fortran như thế này:

extern "C" void FORTRAN_ROUTINE (unsigned & flag); 

Mã này đã làm việc trong một thời gian nhưng (tất nhiên đúng khi Tôi cần phải rời đi) đột nhiên bắt đầu thổi vào một cuộc gọi trở lại. Dấu hiệu rõ ràng của một ngăn xếp cuộc gọi.

kỹ sư khác đến phía sau tôi và cố định các vấn đề, tuyên bố rằng thói quen được định nghĩa trong C++ như

extern "C" void FORTRAN_ROUTINE (unsigned * flag); 

tôi muốn chấp nhận rằng ngoại trừ hai điều. Một là nó có vẻ khá trực quan đối với trình biên dịch để không vượt qua các tham số tham chiếu bằng cách tham chiếu và tôi không thể tìm thấy tài liệu nào ở bất kỳ đâu nói điều đó. Cách khác là anh ta đã thay đổi toàn bộ các mã khác trong cùng một lúc, vì vậy về mặt lý thuyết có thể là một thay đổi khác đã khắc phục được vấn đề.

Vì vậy, câu hỏi là, C++ thực sự vượt qua tham số tham chiếu như thế nào? Có thể tự do sao chép, sao chép cho các giá trị nhỏ hay gì đó không? Nói cách khác, các thông số tham chiếu hoàn toàn vô dụng trong lập trình ngôn ngữ hỗn hợp không? Tôi muốn biết vì vậy tôi không làm cho cùng một sai lầm mã giết chết bao giờ trở lại.

+0

tham chiếu và con trỏ là điều tương tự khi bạn vượt qua giai đoạn xác thực: http://stackoverflow.com/questions/1640355/whats-the-low-level-difference-between-a-pointer-an-a- tham khảo –

+0

@CrazyJugglerDrummer: Từ những gì tôi đọc ở đó, câu trả lời thực sự là "không cần thiết, nhưng với hầu hết các trình biên dịch ngày nay có". Tôi muốn đi xa như vậy để nói rằng đó là cùng một câu hỏi như tôi, ngoại trừ việc tôi thêm vào các nếp nhăn của việc có thói quen trong câu hỏi được định nghĩa là 'extern" C "'. Điều đó có lẽ kỳ diệu làm cho họ giống nhau không? –

Trả lời

4

Chỉ để kêu vang, tôi tin rằng bạn đã đúng. Tôi sử dụng tài liệu tham khảo cho các tham số truyền tới các hàm Fortran mọi lúc. Theo kinh nghiệm của tôi, việc sử dụng các tham chiếu hoặc con trỏ tại giao diện Fortran-C++ là tương đương. Tôi đã thử điều này bằng cách sử dụng GCC/Gfortran, và Visual Studio/Intel Visual Fortran. Nó có thể được trình biên dịch phụ thuộc, nhưng tôi nghĩ về cơ bản tất cả các trình biên dịch thực hiện tài liệu tham khảo bằng cách đi qua con trỏ.

+0

Chúng tôi đang sử dụng VS/IVF là tốt, vì vậy đây là một nhận xét rất hữu ích cho tôi. –

9

C++ không xác định cách triển khai, đó chỉ là ngôn ngữ. Vì vậy, không có "a" triển khai các tham chiếu.

Điều đó nói rằng, tham chiếu được triển khai bằng con trỏ. Điều này dẫn đến rất nhiều sự nhầm lẫn ("tài liệu tham khảo chỉ là con trỏ", "tài liệu tham khảo chỉ là con trỏ với các phần trần tục đưa ra") nhưng đó là không trường hợp. Tham chiếu là bí danh và sẽ luôn là bí danh.

Trình biên dịch sẽ chuyển địa chỉ của một biến và hoạt động với con trỏ đó. Điều này có tác dụng tương tự (nhưng không có cùng ngữ nghĩa!). Để cụ thể hơn, một trình biên dịch có thể "thay thế" này:

void make_five(int& i) 
{ 
    i = 5; 
} 

int main(void) 
{ 
    int i = 0; 
    make_five(i); 
} 

Với điều này:

void make_five(int* const i) 
{ 
    *i = 5; 
} 

int main(void) 
{ 
    int i = 0; 
    make_five(&i); 
} 

(Trên thực tế một chức năng đơn giản như vậy sẽ được sắp xếp theo hàng nhưng bạn sẽ có được điểm.) Do đó tại sao đồng nghiệp của bạn đề nghị bạn sử dụng một con trỏ.

Lưu ý rằng tài liệu tham khảo cần được ưu tiên. Đây là nơi mà sự khác biệt giữa tài liệu tham khảo và con trỏ là quan trọng. Bạn có muốn bí danh một biến, hay bạn muốn chỉ vào nó? Hầu hết các lần, trước đây. Trong C, bạn đã phải sử dụng một con trỏ để làm điều này, và điều này góp phần vào quan niệm sai lầm lập trình C phổ biến mà các tham chiếu thực sự là con trỏ.

Để có được ngữ nghĩa tương tự (kể từ khi bạn đang trỏ đến một biến, và không răng cưa nó), bạn nên đảm bảo giá trị của con trỏ không phải là null:

extern "C" void FORTRAN_ROUTINE (unsigned * flag) 
{ 
    assert(flag); // this is normally not a problem with references, 
        // since the address of a variable cannot be null. 

    // continue... 
} 

Chỉ cần để được an toàn.

+0

OK, nhưng đó là những gì tôi đã mong đợi nó làm. Nếu đó là đâm, sau đó nó phải được làm cái gì khác. Điều đó có thể không? Điều gì sẽ là cái gì khác? –

+0

Lưu ý rằng khai báo 'extern' chỉ là một sơ khai. Việc triển khai ở Fortran. Phía Fortran là * không * đi qua một con trỏ. Nó đi qua một 'logic * 4' (boolean bốn byte) * theo tham chiếu *. Nó sẽ không bao giờ là một con trỏ rỗng. Điều đó được đảm bảo. Đó là lý do tại sao nó có vẻ như một tham số tham chiếu sẽ phù hợp hơn một con trỏ. –

+0

@ T.E.D .: Tôi chưa xử lý giao tiếp với Fortran, nhưng nó có khả năng xuất khẩu theo phong cách C bằng cách sử dụng con trỏ để chuyển qua tham chiếu và chỉ xảy ra để làm việc cho bạn trong một số trường hợp nhất định. –

2

Về lý thuyết, trong C++ được tham chiếu được hiển thị dưới dạng con trỏ bình thường. Trình biên dịch sau đó thay đổi mã cho hàm chức năng như một tham chiếu, nhưng tải địa chỉ và sau đó di chuyển gián tiếp địa chỉ.

Đây là một ứng dụng nhỏ:

void foo(int & value) 
{ 
    value = 3; 
} 


void bar(int *value) 
{ 
    *value = 3; 
} 

void do_test() 
{ 
    int i; 
    foo(i); 
    bar(&i); 
} 

Hãy lắp ráp nó và nhìn vào gcc tạo lắp ráp (gcc -s):

 .file "test-params.cpp" 
     .text 
.globl _Z3fooRi 
     .type _Z3fooRi, @function 
_Z3fooRi: 
.LFB0: 
     .cfi_startproc 
     .cfi_personality 0x0,__gxx_personality_v0 
     pushl %ebp 
     .cfi_def_cfa_offset 8 
     movl %esp, %ebp 
     .cfi_offset 5, -8 
     .cfi_def_cfa_register 5 
     movl 8(%ebp), %eax 
     movl $3, (%eax) 
     popl %ebp 
     ret 
     .cfi_endproc 
.LFE0: 
     .size _Z3fooRi, .-_Z3fooRi 
.globl _Z3barPi 
     .type _Z3barPi, @function 
_Z3barPi: 
.LFB1: 
     .cfi_startproc 
     .cfi_personality 0x0,__gxx_personality_v0 
     pushl %ebp 
     .cfi_def_cfa_offset 8 
     movl %esp, %ebp 
     .cfi_offset 5, -8 
     .cfi_def_cfa_register 5 
     movl 8(%ebp), %eax 
     movl $3, (%eax) 
     popl %ebp 
     ret 
     .cfi_endproc 
.LFE1: 
     .size _Z3barPi, .-_Z3barPi 
.globl _Z7do_testv 
     .type _Z7do_testv, @function 
_Z7do_testv: 
.LFB2: 
     .cfi_startproc 
     .cfi_personality 0x0,__gxx_personality_v0 
     pushl %ebp 
     .cfi_def_cfa_offset 8 
     movl %esp, %ebp 
     .cfi_offset 5, -8 
     .cfi_def_cfa_register 5 
     subl $20, %esp 
     leal -4(%ebp), %eax 
     movl %eax, (%esp) 
     call _Z3fooRi 
     leal -4(%ebp), %eax 
     movl %eax, (%esp) 
     call _Z3barPi 
     leave 
     ret 
     .cfi_endproc 
.LFE2: 
     .size _Z7do_testv, .-_Z7do_testv 
     .ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3" 
     .section  .note.GNU-stack,"",@progbits 

Như bạn có thể thấy, trong cả hai chức năng, các trình biên dịch đọc (stack movl 8(%ebp), %eax), và trong cả hai cuộc gọi trình biên dịch lưu địa chỉ vào ngăn xếp (leal -4(%ebp), %eax).

Câu trả lời GMan - Lưu Unico đưa ra một bout C tuyên bố có thể là vấn đề. Có vẻ như vấn đề là khả năng tương tác giữa C và fortran (ít nhất hai trình biên dịch bạn đang sử dụng).

1

Không có sự khác biệt.

unsigned & cờ

là chính xác như bạn sẽ viết

unsigned * const cờ

trừ các nhà khai thác để truy cập vào các thành viên đối tượng ("" và "->" tương ứng).

Các vấn đề liên quan